Char/Misc driver patches for 4.2-rc1

Here's the big char/misc driver pull request for 4.2-rc1.
 
 Lots of mei, extcon, coresight, uio, mic, and other driver updates in
 here.  Full details in the shortlog.  All of these have been in
 linux-next for some time with no reported problems.
 
 Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
 -----BEGIN PGP SIGNATURE-----
 Version: GnuPG v2
 
 iEYEABECAAYFAlWNn0gACgkQMUfUDdst+ykCCQCgvdF4F2+Hy9+RATdk22ak1uq1
 JDMAoJTf4oyaIEdaiOKfEIWg9MasS42B
 =H5wD
 -----END PGP SIGNATURE-----

Merge tag 'char-misc-4.2-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc

Pull char/misc driver updates from Greg KH:
 "Here's the big char/misc driver pull request for 4.2-rc1.

  Lots of mei, extcon, coresight, uio, mic, and other driver updates in
  here.  Full details in the shortlog.  All of these have been in
  linux-next for some time with no reported problems"

* tag 'char-misc-4.2-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc: (176 commits)
  mei: me: wait for power gating exit confirmation
  mei: reset flow control on the last client disconnection
  MAINTAINERS: mei: add mei_cl_bus.h to maintained file list
  misc: sram: sort and clean up included headers
  misc: sram: move reserved block logic out of probe function
  misc: sram: add private struct device and virt_base members
  misc: sram: report correct SRAM pool size
  misc: sram: bump error message level on unclean driver unbinding
  misc: sram: fix device node reference leak on error
  misc: sram: fix enabled clock leak on error path
  misc: mic: Fix reported static checker warning
  misc: mic: Fix randconfig build error by including errno.h
  uio: pruss: Drop depends on ARCH_DAVINCI_DA850 from config
  uio: pruss: Add CONFIG_HAS_IOMEM dependence
  uio: pruss: Include <linux/sizes.h>
  extcon: Redefine the unique id of supported external connectors without 'enum extcon' type
  char:xilinx_hwicap:buffer_icap - change 1/0 to true/false for bool type variable in function buffer_icap_set_configuration().
  Drivers: hv: vmbus: Allocate ring buffer memory in NUMA aware fashion
  parport: check exclusive access before register
  w1: use correct lock on error in w1_seq_show()
  ...
This commit is contained in:
Linus Torvalds 2015-06-26 14:51:15 -07:00
commit d87823813f
172 changed files with 15494 additions and 5662 deletions

View File

@ -0,0 +1,11 @@
What: /sys/bus/w1/devices/.../w1_master_timeout_us
Date: April 2015
Contact: Dmitry Khromov <dk@icelogic.net>
Description: Bus scanning interval, microseconds component.
Some of 1-Wire devices commonly associated with physical access
control systems are attached/generate presence for as short as
100 ms - hence the tens-to-hundreds milliseconds scan intervals
are required.
see Documentation/w1/w1.generic for detailed information.
Users: any user space application which wants to know bus scanning
interval

View File

@ -0,0 +1,6 @@
What: /sys/bus/w1/devices/.../w1_seq
Date: Apr 2015
Contact: Matt Campbell <mattrcampbell@gmail.com>
Description: Support for the DS28EA00 chain sequence function
see Documentation/w1/slaves/w1_therm for detailed information
Users: any user space application which wants to communicate with DS28EA00

View File

@ -0,0 +1,450 @@
What: /sys/bus/coresight/devices/<memory_map>.etm/enable_source
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (RW) Enable/disable tracing on this specific trace entiry.
Enabling a source implies the source has been configured
properly and a sink has been identidifed for it. The path
of coresight components linking the source to the sink is
configured and managed automatically by the coresight framework.
What: /sys/bus/coresight/devices/<memory_map>.etm/cpu
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (R) The CPU this tracing entity is associated with.
What: /sys/bus/coresight/devices/<memory_map>.etm/nr_pe_cmp
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (R) Indicates the number of PE comparator inputs that are
available for tracing.
What: /sys/bus/coresight/devices/<memory_map>.etm/nr_addr_cmp
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (R) Indicates the number of address comparator pairs that are
available for tracing.
What: /sys/bus/coresight/devices/<memory_map>.etm/nr_cntr
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (R) Indicates the number of counters that are available for
tracing.
What: /sys/bus/coresight/devices/<memory_map>.etm/nr_ext_inp
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (R) Indicates how many external inputs are implemented.
What: /sys/bus/coresight/devices/<memory_map>.etm/numcidc
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (R) Indicates the number of Context ID comparators that are
available for tracing.
What: /sys/bus/coresight/devices/<memory_map>.etm/numvmidc
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (R) Indicates the number of VMID comparators that are available
for tracing.
What: /sys/bus/coresight/devices/<memory_map>.etm/nrseqstate
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (R) Indicates the number of sequencer states that are
implemented.
What: /sys/bus/coresight/devices/<memory_map>.etm/nr_resource
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (R) Indicates the number of resource selection pairs that are
available for tracing.
What: /sys/bus/coresight/devices/<memory_map>.etm/nr_ss_cmp
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (R) Indicates the number of single-shot comparator controls that
are available for tracing.
What: /sys/bus/coresight/devices/<memory_map>.etm/reset
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (W) Cancels all configuration on a trace unit and set it back
to its boot configuration.
What: /sys/bus/coresight/devices/<memory_map>.etm/mode
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (RW) Controls various modes supported by this ETM, for example
P0 instruction tracing, branch broadcast, cycle counting and
context ID tracing.
What: /sys/bus/coresight/devices/<memory_map>.etm/pe
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (RW) Controls which PE to trace.
What: /sys/bus/coresight/devices/<memory_map>.etm/event
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (RW) Controls the tracing of arbitrary events from bank 0 to 3.
What: /sys/bus/coresight/devices/<memory_map>.etm/event_instren
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (RW) Controls the behavior of the events in bank 0 to 3.
What: /sys/bus/coresight/devices/<memory_map>.etm/event_ts
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (RW) Controls the insertion of global timestamps in the trace
streams.
What: /sys/bus/coresight/devices/<memory_map>.etm/syncfreq
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (RW) Controls how often trace synchronization requests occur.
What: /sys/bus/coresight/devices/<memory_map>.etm/cyc_threshold
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (RW) Sets the threshold value for cycle counting.
What: /sys/bus/coresight/devices/<memory_map>.etm/bb_ctrl
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (RW) Controls which regions in the memory map are enabled to
use branch broadcasting.
What: /sys/bus/coresight/devices/<memory_map>.etm/event_vinst
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (RW) Controls instruction trace filtering.
What: /sys/bus/coresight/devices/<memory_map>.etm/s_exlevel_vinst
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (RW) In Secure state, each bit controls whether instruction
tracing is enabled for the corresponding exception level.
What: /sys/bus/coresight/devices/<memory_map>.etm/ns_exlevel_vinst
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (RW) In non-secure state, each bit controls whether instruction
tracing is enabled for the corresponding exception level.
What: /sys/bus/coresight/devices/<memory_map>.etm/addr_idx
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (RW) Select which address comparator or pair (of comparators) to
work with.
What: /sys/bus/coresight/devices/<memory_map>.etm/addr_instdatatype
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (RW) Controls what type of comparison the trace unit performs.
What: /sys/bus/coresight/devices/<memory_map>.etm/addr_single
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (RW) Used to setup single address comparator values.
What: /sys/bus/coresight/devices/<memory_map>.etm/addr_range
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (RW) Used to setup address range comparator values.
What: /sys/bus/coresight/devices/<memory_map>.etm/seq_idx
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (RW) Select which sequensor.
What: /sys/bus/coresight/devices/<memory_map>.etm/seq_state
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (RW) Use this to set, or read, the sequencer state.
What: /sys/bus/coresight/devices/<memory_map>.etm/seq_event
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (RW) Moves the sequencer state to a specific state.
What: /sys/bus/coresight/devices/<memory_map>.etm/seq_reset_event
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (RW) Moves the sequencer to state 0 when a programmed event
occurs.
What: /sys/bus/coresight/devices/<memory_map>.etm/cntr_idx
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (RW) Select which counter unit to work with.
What: /sys/bus/coresight/devices/<memory_map>.etm/cntrldvr
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (RW) This sets or returns the reload count value of the
specific counter.
What: /sys/bus/coresight/devices/<memory_map>.etm/cntr_val
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (RW) This sets or returns the current count value of the
specific counter.
What: /sys/bus/coresight/devices/<memory_map>.etm/cntr_ctrl
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (RW) Controls the operation of the selected counter.
What: /sys/bus/coresight/devices/<memory_map>.etm/res_idx
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (RW) Select which resource selection unit to work with.
What: /sys/bus/coresight/devices/<memory_map>.etm/res_ctrl
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (RW) Controls the selection of the resources in the trace unit.
What: /sys/bus/coresight/devices/<memory_map>.etm/ctxid_idx
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (RW) Select which context ID comparator to work with.
What: /sys/bus/coresight/devices/<memory_map>.etm/ctxid_val
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (RW) Get/Set the context ID comparator value to trigger on.
What: /sys/bus/coresight/devices/<memory_map>.etm/ctxid_masks
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (RW) Mask for all 8 context ID comparator value
registers (if implemented).
What: /sys/bus/coresight/devices/<memory_map>.etm/vmid_idx
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (RW) Select which virtual machine ID comparator to work with.
What: /sys/bus/coresight/devices/<memory_map>.etm/vmid_val
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (RW) Get/Set the virtual machine ID comparator value to
trigger on.
What: /sys/bus/coresight/devices/<memory_map>.etm/vmid_masks
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (RW) Mask for all 8 virtual machine ID comparator value
registers (if implemented).
What: /sys/bus/coresight/devices/<memory_map>.etm/mgmt/trcoslsr
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (R) Print the content of the OS Lock Status Register (0x304).
The value it taken directly from the HW.
What: /sys/bus/coresight/devices/<memory_map>.etm/mgmt/trcpdcr
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (R) Print the content of the Power Down Control Register
(0x310). The value is taken directly from the HW.
What: /sys/bus/coresight/devices/<memory_map>.etm/mgmt/trcpdsr
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (R) Print the content of the Power Down Status Register
(0x314). The value is taken directly from the HW.
What: /sys/bus/coresight/devices/<memory_map>.etm/mgmt/trclsr
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (R) Print the content of the SW Lock Status Register
(0xFB4). The value is taken directly from the HW.
What: /sys/bus/coresight/devices/<memory_map>.etm/mgmt/trcauthstatus
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (R) Print the content of the Authentication Status Register
(0xFB8). The value is taken directly from the HW.
What: /sys/bus/coresight/devices/<memory_map>.etm/mgmt/trcdevid
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (R) Print the content of the Device ID Register
(0xFC8). The value is taken directly from the HW.
What: /sys/bus/coresight/devices/<memory_map>.etm/mgmt/trcdevtype
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (R) Print the content of the Device Type Register
(0xFCC). The value is taken directly from the HW.
What: /sys/bus/coresight/devices/<memory_map>.etm/mgmt/trcpidr0
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (R) Print the content of the Peripheral ID0 Register
(0xFE0). The value is taken directly from the HW.
What: /sys/bus/coresight/devices/<memory_map>.etm/mgmt/trcpidr1
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (R) Print the content of the Peripheral ID1 Register
(0xFE4). The value is taken directly from the HW.
What: /sys/bus/coresight/devices/<memory_map>.etm/mgmt/trcpidr2
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (R) Print the content of the Peripheral ID2 Register
(0xFE8). The value is taken directly from the HW.
What: /sys/bus/coresight/devices/<memory_map>.etm/mgmt/trcpidr3
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (R) Print the content of the Peripheral ID3 Register
(0xFEC). The value is taken directly from the HW.
What: /sys/bus/coresight/devices/<memory_map>.etm/trcidr/trcidr0
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (R) Returns the tracing capabilities of the trace unit (0x1E0).
The value is taken directly from the HW.
What: /sys/bus/coresight/devices/<memory_map>.etm/trcidr/trcidr1
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (R) Returns the tracing capabilities of the trace unit (0x1E4).
The value is taken directly from the HW.
What: /sys/bus/coresight/devices/<memory_map>.etm/trcidr/trcidr2
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (R) Returns the maximum size of the data value, data address,
VMID, context ID and instuction address in the trace unit
(0x1E8). The value is taken directly from the HW.
What: /sys/bus/coresight/devices/<memory_map>.etm/trcidr/trcidr3
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (R) Returns the value associated with various resources
available to the trace unit. See the Trace Macrocell
architecture specification for more details (0x1E8).
The value is taken directly from the HW.
What: /sys/bus/coresight/devices/<memory_map>.etm/trcidr/trcidr4
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (R) Returns how many resources the trace unit supports (0x1F0).
The value is taken directly from the HW.
What: /sys/bus/coresight/devices/<memory_map>.etm/trcidr/trcidr5
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (R) Returns how many resources the trace unit supports (0x1F4).
The value is taken directly from the HW.
What: /sys/bus/coresight/devices/<memory_map>.etm/trcidr/trcidr8
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (R) Returns the maximum speculation depth of the instruction
trace stream. (0x180). The value is taken directly from the HW.
What: /sys/bus/coresight/devices/<memory_map>.etm/trcidr/trcidr9
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (R) Returns the number of P0 right-hand keys that the trace unit
can use (0x184). The value is taken directly from the HW.
What: /sys/bus/coresight/devices/<memory_map>.etm/trcidr/trcidr10
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (R) Returns the number of P1 right-hand keys that the trace unit
can use (0x188). The value is taken directly from the HW.
What: /sys/bus/coresight/devices/<memory_map>.etm/trcidr/trcidr11
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (R) Returns the number of special P1 right-hand keys that the
trace unit can use (0x18C). The value is taken directly from
the HW.
What: /sys/bus/coresight/devices/<memory_map>.etm/trcidr/trcidr12
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (R) Returns the number of conditional P1 right-hand keys that
the trace unit can use (0x190). The value is taken directly
from the HW.
What: /sys/bus/coresight/devices/<memory_map>.etm/trcidr/trcidr13
Date: April 2015
KernelVersion: 4.01
Contact: Mathieu Poirier <mathieu.poirier@linaro.org>
Description: (R) Returns the number of special conditional P1 right-hand keys
that the trace unit can use (0x194). The value is taken
directly from the HW.

View File

@ -4,4 +4,18 @@ KernelVersion: 3.10
Contact: Samuel Ortiz <sameo@linux.intel.com>
linux-mei@linux.intel.com
Description: Stores the same MODALIAS value emitted by uevent
Format: mei:<mei device name>
Format: mei:<mei device name>:<device uuid>:
What: /sys/bus/mei/devices/.../name
Date: May 2015
KernelVersion: 4.2
Contact: Tomas Winkler <tomas.winkler@intel.com>
Description: Stores mei client device name
Format: string
What: /sys/bus/mei/devices/.../uuid
Date: May 2015
KernelVersion: 4.2
Contact: Tomas Winkler <tomas.winkler@intel.com>
Description: Stores mei client device uuid
Format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

View File

@ -17,15 +17,19 @@ its hardware characteristcs.
- "arm,coresight-tmc", "arm,primecell";
- "arm,coresight-funnel", "arm,primecell";
- "arm,coresight-etm3x", "arm,primecell";
- "qcom,coresight-replicator1x", "arm,primecell";
* reg: physical base address and length of the register
set(s) of the component.
* clocks: the clock associated to this component.
* clocks: the clocks associated to this component.
* clock-names: the name of the clock as referenced by the code.
Since we are using the AMBA framework, the name should be
"apb_pclk".
* clock-names: the name of the clocks referenced by the code.
Since we are using the AMBA framework, the name of the clock
providing the interconnect should be "apb_pclk", and some
coresight blocks also have an additional clock "atclk", which
clocks the core of that coresight component. The latter clock
is optional.
* port or ports: The representation of the component's port
layout using the generic DT graph presentation found in

View File

@ -67,6 +67,12 @@ Optional properties:
present, the number of values should be less than or equal to the
number of inputs, unspecified inputs will use the chip default.
- wlf,hpdet-channel : Headphone detection channel.
ARIZONA_ACCDET_MODE_HPL or 1 - Headphone detect mode is set to HPDETL
ARIZONA_ACCDET_MODE_HPR or 2 - Headphone detect mode is set to HPDETR
If this node is not mentioned or if the value is unknown, then
headphone detection mode is set to HPDETL.
- DCVDD-supply, MICVDD-supply : Power supplies, only need to be specified if
they are being externally supplied. As covered in
Documentation/devicetree/bindings/regulator/regulator.txt

View File

@ -24,6 +24,10 @@ a virtual bus called mic bus is created and virtual dma devices are
created on it by the host/card drivers. On host the channels are private
and used only by the host driver to transfer data for the virtio devices.
The Symmetric Communication Interface (SCIF (pronounced as skiff)) is a
low level communications API across PCIe currently implemented for MIC.
More details are available at scif_overview.txt.
Here is a block diagram of the various components described above. The
virtio backends are situated on the host rather than the card given better
single threaded performance for the host compared to MIC, the ability of
@ -47,18 +51,18 @@ the fact that the virtio block storage backend can only be on the host.
| | | Virtio over PCIe IOCTLs |
| | +--------------------------+
+-----------+ | | | +-----------+
| MIC DMA | | | | | MIC DMA |
| Driver | | | | | Driver |
+-----------+ | | | +-----------+
| | | | |
+---------------+ | | | +----------------+
|MIC virtual Bus| | | | |MIC virtual Bus |
+---------------+ | | | +----------------+
| | | | |
| +--------------+ | +---------------+ |
| |Intel MIC | | |Intel MIC | |
+---|Card Driver | | |Host Driver | |
+--------------+ | +---------------+-----+
| MIC DMA | | +----------+ | +-----------+ | | MIC DMA |
| Driver | | | SCIF | | | SCIF | | | Driver |
+-----------+ | +----------+ | +-----------+ | +-----------+
| | | | | | |
+---------------+ | +-----+-----+ | +-----+-----+ | +---------------+
|MIC virtual Bus| | |SCIF HW Bus| | |SCIF HW BUS| | |MIC virtual Bus|
+---------------+ | +-----------+ | +-----+-----+ | +---------------+
| | | | | | |
| +--------------+ | | | +---------------+ |
| |Intel MIC | | | | |Intel MIC | |
+---|Card Driver +----+ | | |Host Driver | |
+--------------+ | +----+---------------+-----+
| | |
+-------------------------------------------------------------+
| |

View File

@ -35,6 +35,7 @@
exec=/usr/sbin/mpssd
sysfs="/sys/class/mic"
mic_modules="mic_host mic_x100_dma scif"
start()
{
@ -48,18 +49,15 @@ start()
fi
echo -e $"Starting MPSS Stack"
echo -e $"Loading MIC_X100_DMA & MIC_HOST Modules"
echo -e $"Loading MIC drivers:" $mic_modules
for f in "mic_host" "mic_x100_dma"
do
modprobe $f
RETVAL=$?
if [ $RETVAL -ne 0 ]; then
failure
echo
return $RETVAL
fi
done
modprobe -a $mic_modules
RETVAL=$?
if [ $RETVAL -ne 0 ]; then
failure
echo
return $RETVAL
fi
# Start the daemon
echo -n $"Starting MPSSD "
@ -170,8 +168,8 @@ unload()
stop
sleep 5
echo -n $"Removing MIC_HOST & MIC_X100_DMA Modules: "
modprobe -r mic_host mic_x100_dma
echo -n $"Removing MIC drivers:" $mic_modules
modprobe -r $mic_modules
RETVAL=$?
[ $RETVAL -ne 0 ] && failure || success
echo

View File

@ -0,0 +1,98 @@
The Symmetric Communication Interface (SCIF (pronounced as skiff)) is a low
level communications API across PCIe currently implemented for MIC. Currently
SCIF provides inter-node communication within a single host platform, where a
node is a MIC Coprocessor or Xeon based host. SCIF abstracts the details of
communicating over the PCIe bus while providing an API that is symmetric
across all the nodes in the PCIe network. An important design objective for SCIF
is to deliver the maximum possible performance given the communication
abilities of the hardware. SCIF has been used to implement an offload compiler
runtime and OFED support for MPI implementations for MIC coprocessors.
==== SCIF API Components ====
The SCIF API has the following parts:
1. Connection establishment using a client server model
2. Byte stream messaging intended for short messages
3. Node enumeration to determine online nodes
4. Poll semantics for detection of incoming connections and messages
5. Memory registration to pin down pages
6. Remote memory mapping for low latency CPU accesses via mmap
7. Remote DMA (RDMA) for high bandwidth DMA transfers
8. Fence APIs for RDMA synchronization
SCIF exposes the notion of a connection which can be used by peer processes on
nodes in a SCIF PCIe "network" to share memory "windows" and to communicate. A
process in a SCIF node initiates a SCIF connection to a peer process on a
different node via a SCIF "endpoint". SCIF endpoints support messaging APIs
which are similar to connection oriented socket APIs. Connected SCIF endpoints
can also register local memory which is followed by data transfer using either
DMA, CPU copies or remote memory mapping via mmap. SCIF supports both user and
kernel mode clients which are functionally equivalent.
==== SCIF Performance for MIC ====
DMA bandwidth comparison between the TCP (over ethernet over PCIe) stack versus
SCIF shows the performance advantages of SCIF for HPC applications and runtimes.
Comparison of TCP and SCIF based BW
Throughput (GB/sec)
8 + PCIe Bandwidth ******
+ TCP ######
7 + ************************************** SCIF %%%%%%
| %%%%%%%%%%%%%%%%%%%
6 + %%%%
| %%
| %%%
5 + %%
| %%
4 + %%
| %%
3 + %%
| %
2 + %%
| %%
| %
1 +
+ ######################################
0 +++---+++--+--+-+--+--+-++-+--+-++-+--+-++-+-
1 10 100 1000 10000 100000
Transfer Size (KBytes)
SCIF allows memory sharing via mmap(..) between processes on different PCIe
nodes and thus provides bare-metal PCIe latency. The round trip SCIF mmap
latency from the host to an x100 MIC for an 8 byte message is 0.44 usecs.
SCIF has a user space library which is a thin IOCTL wrapper providing a user
space API similar to the kernel API in scif.h. The SCIF user space library
is distributed @ https://software.intel.com/en-us/mic-developer
Here is some pseudo code for an example of how two applications on two PCIe
nodes would typically use the SCIF API:
Process A (on node A) Process B (on node B)
/* get online node information */
scif_get_node_ids(..) scif_get_node_ids(..)
scif_open(..) scif_open(..)
scif_bind(..) scif_bind(..)
scif_listen(..)
scif_accept(..) scif_connect(..)
/* SCIF connection established */
/* Send and receive short messages */
scif_send(..)/scif_recv(..) scif_send(..)/scif_recv(..)
/* Register memory */
scif_register(..) scif_register(..)
/* RDMA */
scif_readfrom(..)/scif_writeto(..) scif_readfrom(..)/scif_writeto(..)
/* Fence DMAs */
scif_fence_signal(..) scif_fence_signal(..)
mmap(..) mmap(..)
/* Access remote registered memory */
/* Close the endpoints */
scif_close(..) scif_close(..)

View File

@ -11,12 +11,14 @@ Author: Evgeniy Polyakov <johnpol@2ka.mipt.ru>
Description
-----------
w1_therm provides basic temperature conversion for ds18*20 devices.
w1_therm provides basic temperature conversion for ds18*20 devices, and the
ds28ea00 device.
supported family codes:
W1_THERM_DS18S20 0x10
W1_THERM_DS1822 0x22
W1_THERM_DS18B20 0x28
W1_THERM_DS1825 0x3B
W1_THERM_DS28EA00 0x42
Support is provided through the sysfs w1_slave file. Each open and
read sequence will initiate a temperature conversion then provide two
@ -48,3 +50,10 @@ resistor). The DS18b20 temperature sensor specification lists a
maximum current draw of 1.5mA and that a 5k pullup resistor is not
sufficient. The strong pullup is designed to provide the additional
current required.
The DS28EA00 provides an additional two pins for implementing a sequence
detection algorithm. This feature allows you to determine the physical
location of the chip in the 1-wire bus without needing pre-existing
knowledge of the bus ordering. Support is provided through the sysfs
w1_seq file. The file will contain a single line with an integer value
representing the device index in the bus starting at 0.

View File

@ -76,21 +76,24 @@ See struct w1_bus_master definition in w1.h for details.
w1 master sysfs interface
------------------------------------------------------------------
<xx-xxxxxxxxxxxxx> - a directory for a found device. The format is family-serial
<xx-xxxxxxxxxxxxx> - A directory for a found device. The format is family-serial
bus - (standard) symlink to the w1 bus
driver - (standard) symlink to the w1 driver
w1_master_add - Manually register a slave device
w1_master_attempts - the number of times a search was attempted
w1_master_add - (rw) manually register a slave device
w1_master_attempts - (ro) the number of times a search was attempted
w1_master_max_slave_count
- maximum number of slaves to search for at a time
w1_master_name - the name of the device (w1_bus_masterX)
w1_master_pullup - 5V strong pullup 0 enabled, 1 disabled
w1_master_remove - Manually remove a slave device
w1_master_search - the number of searches left to do, -1=continual (default)
- (rw) maximum number of slaves to search for at a time
w1_master_name - (ro) the name of the device (w1_bus_masterX)
w1_master_pullup - (rw) 5V strong pullup 0 enabled, 1 disabled
w1_master_remove - (rw) manually remove a slave device
w1_master_search - (rw) the number of searches left to do,
-1=continual (default)
w1_master_slave_count
- the number of slaves found
w1_master_slaves - the names of the slaves, one per line
w1_master_timeout - the delay in seconds between searches
- (ro) the number of slaves found
w1_master_slaves - (ro) the names of the slaves, one per line
w1_master_timeout - (ro) the delay in seconds between searches
w1_master_timeout_us
- (ro) the delay in microseconds beetwen searches
If you have a w1 bus that never changes (you don't add or remove devices),
you can set the module parameter search_count to a small positive number
@ -101,6 +104,11 @@ generally only make sense when searching is disabled, as a search will
redetect manually removed devices that are present and timeout manually
added devices that aren't on the bus.
Bus searches occur at an interval, specified as a summ of timeout and
timeout_us module parameters (either of which may be 0) for as long as
w1_master_search remains greater than 0 or is -1. Each search attempt
decrements w1_master_search by 1 (down to 0) and increments
w1_master_attempts by 1.
w1 slave sysfs interface
------------------------------------------------------------------

View File

@ -732,7 +732,7 @@ ANDROID DRIVERS
M: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
M: Arve Hjønnevåg <arve@android.com>
M: Riley Andrews <riandrews@android.com>
T: git git://git.kernel.org/pub/scm/linux/kernel/gregkh/staging.git
T: git git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging.git
L: devel@driverdev.osuosl.org
S: Supported
F: drivers/android/
@ -3207,9 +3207,9 @@ S: Maintained
F: drivers/platform/x86/dell-smo8800.c
DELL LAPTOP SMM DRIVER
M: Guenter Roeck <linux@roeck-us.net>
M: Pali Rohár <pali.rohar@gmail.com>
S: Maintained
F: drivers/char/i8k.c
F: drivers/hwmon/dell-smm-hwmon.c
F: include/uapi/linux/i8k.h
DELL SYSTEMS MANAGEMENT BASE DRIVER (dcdbas)
@ -5444,6 +5444,7 @@ M: Tomas Winkler <tomas.winkler@intel.com>
L: linux-kernel@vger.kernel.org
S: Supported
F: include/uapi/linux/mei.h
F: include/linux/mei_cl_bus.h
F: drivers/misc/mei/*
F: Documentation/misc-devices/mei/*
@ -7572,13 +7573,16 @@ S: Maintained
F: Documentation/mn10300/
F: arch/mn10300/
PARALLEL PORT SUPPORT
PARALLEL PORT SUBSYSTEM
M: Sudip Mukherjee <sudipm.mukherjee@gmail.com>
M: Sudip Mukherjee <sudip@vectorindia.org>
L: linux-parport@lists.infradead.org (subscribers-only)
S: Orphan
S: Maintained
F: drivers/parport/
F: include/linux/parport*.h
F: drivers/char/ppdev.c
F: include/uapi/linux/ppdev.h
F: Documentation/parport*.txt
PARAVIRT_OPS INTERFACE
M: Jeremy Fitzhardinge <jeremy@goop.org>

View File

@ -105,7 +105,7 @@ static int etap_tramp(char *dev, char *gate, int control_me,
sprintf(data_fd_buf, "%d", data_remote);
sprintf(version_buf, "%d", UML_NET_VERSION);
if (gate != NULL) {
strcpy(gate_buf, gate);
strncpy(gate_buf, gate, 15);
args = setup_args;
}
else args = nosetup_args;

View File

@ -1055,24 +1055,19 @@ config TOSHIBA
Say N otherwise.
config I8K
tristate "Dell laptop support"
tristate "Dell i8k legacy laptop support"
select HWMON
select SENSORS_DELL_SMM
---help---
This adds a driver to safely access the System Management Mode
of the CPU on the Dell Inspiron 8000. The System Management Mode
is used to read cpu temperature and cooling fan status and to
control the fans on the I8K portables.
This option enables legacy /proc/i8k userspace interface in hwmon
dell-smm-hwmon driver. Character file /proc/i8k reports bios version,
temperature and allows controlling fan speeds of Dell laptops via
System Management Mode. For old Dell laptops (like Dell Inspiron 8000)
it reports also power and hotkey status. For fan speed control is
needed userspace package i8kutils.
This driver has been tested only on the Inspiron 8000 but it may
also work with other Dell laptops. You can force loading on other
models by passing the parameter `force=1' to the module. Use at
your own risk.
For information on utilities to make use of this driver see the
I8K Linux utilities web site at:
<http://people.debian.org/~dz/i8k/>
Say Y if you intend to run this kernel on a Dell Inspiron 8000.
Say Y if you intend to run this kernel on old Dell laptops or want to
use userspace package i8kutils.
Say N otherwise.
config X86_REBOOTFIXUPS

View File

@ -30,6 +30,7 @@
#include <linux/wait.h>
#include <linux/sched.h> /* TASK_* */
#include <linux/parport.h>
#include <linux/slab.h>
#include "paride.h"
@ -244,17 +245,19 @@ void paride_unregister(PIP * pr)
EXPORT_SYMBOL(paride_unregister);
static int pi_register_parport(PIA * pi, int verbose)
static int pi_register_parport(PIA *pi, int verbose, int unit)
{
struct parport *port;
struct pardev_cb par_cb;
port = parport_find_base(pi->port);
if (!port)
return 0;
pi->pardev = parport_register_device(port,
pi->device, NULL,
pi_wake_up, NULL, 0, (void *) pi);
memset(&par_cb, 0, sizeof(par_cb));
par_cb.wakeup = pi_wake_up;
par_cb.private = (void *)pi;
pi->pardev = parport_register_dev_model(port, pi->device, &par_cb,
unit);
parport_put_port(port);
if (!pi->pardev)
return 0;
@ -311,7 +314,7 @@ static int pi_probe_unit(PIA * pi, int unit, char *scratch, int verbose)
e = pi->proto->max_units;
}
if (!pi_register_parport(pi, verbose))
if (!pi_register_parport(pi, verbose, s))
return 0;
if (pi->proto->test_port) {
@ -432,3 +435,45 @@ int pi_init(PIA * pi, int autoprobe, int port, int mode,
}
EXPORT_SYMBOL(pi_init);
static int pi_probe(struct pardevice *par_dev)
{
struct device_driver *drv = par_dev->dev.driver;
int len = strlen(drv->name);
if (strncmp(par_dev->name, drv->name, len))
return -ENODEV;
return 0;
}
void *pi_register_driver(char *name)
{
struct parport_driver *parp_drv;
int ret;
parp_drv = kzalloc(sizeof(*parp_drv), GFP_KERNEL);
if (!parp_drv)
return NULL;
parp_drv->name = name;
parp_drv->probe = pi_probe;
parp_drv->devmodel = true;
ret = parport_register_driver(parp_drv);
if (ret) {
kfree(parp_drv);
return NULL;
}
return (void *)parp_drv;
}
EXPORT_SYMBOL(pi_register_driver);
void pi_unregister_driver(void *_drv)
{
struct parport_driver *drv = _drv;
parport_unregister_driver(drv);
kfree(drv);
}
EXPORT_SYMBOL(pi_unregister_driver);

View File

@ -165,6 +165,8 @@ typedef struct pi_protocol PIP;
extern int paride_register( PIP * );
extern void paride_unregister ( PIP * );
void *pi_register_driver(char *);
void pi_unregister_driver(void *);
#endif /* __DRIVERS_PARIDE_H__ */
/* end of paride.h */

View File

@ -221,6 +221,7 @@ static int pcd_busy; /* request being processed ? */
static int pcd_sector; /* address of next requested sector */
static int pcd_count; /* number of blocks still to do */
static char *pcd_buf; /* buffer for request in progress */
static void *par_drv; /* reference of parport driver */
/* kernel glue structures */
@ -690,6 +691,12 @@ static int pcd_detect(void)
printk("%s: %s version %s, major %d, nice %d\n",
name, name, PCD_VERSION, major, nice);
par_drv = pi_register_driver(name);
if (!par_drv) {
pr_err("failed to register %s driver\n", name);
return -1;
}
k = 0;
if (pcd_drive_count == 0) { /* nothing spec'd - so autoprobe for 1 */
cd = pcd;
@ -723,6 +730,7 @@ static int pcd_detect(void)
printk("%s: No CD-ROM drive found\n", name);
for (unit = 0, cd = pcd; unit < PCD_UNITS; unit++, cd++)
put_disk(cd->disk);
pi_unregister_driver(par_drv);
return -1;
}
@ -984,6 +992,7 @@ static void __exit pcd_exit(void)
}
blk_cleanup_queue(pcd_queue);
unregister_blkdev(major, name);
pi_unregister_driver(par_drv);
}
MODULE_LICENSE("GPL");

View File

@ -247,6 +247,8 @@ static char *pd_errs[17] = { "ERR", "INDEX", "ECC", "DRQ", "SEEK", "WRERR",
"IDNF", "MC", "UNC", "???", "TMO"
};
static void *par_drv; /* reference of parport driver */
static inline int status_reg(struct pd_unit *disk)
{
return pi_read_regr(disk->pi, 1, 6);
@ -872,6 +874,12 @@ static int pd_detect(void)
pd_drive_count++;
}
par_drv = pi_register_driver(name);
if (!par_drv) {
pr_err("failed to register %s driver\n", name);
return -1;
}
if (pd_drive_count == 0) { /* nothing spec'd - so autoprobe for 1 */
disk = pd;
if (pi_init(disk->pi, 1, -1, -1, -1, -1, -1, pd_scratch,
@ -902,8 +910,10 @@ static int pd_detect(void)
found = 1;
}
}
if (!found)
if (!found) {
printk("%s: no valid drive found\n", name);
pi_unregister_driver(par_drv);
}
return found;
}

View File

@ -264,6 +264,7 @@ static int pf_cmd; /* current command READ/WRITE */
static struct pf_unit *pf_current;/* unit of current request */
static int pf_mask; /* stopper for pseudo-int */
static char *pf_buf; /* buffer for request in progress */
static void *par_drv; /* reference of parport driver */
/* kernel glue structures */
@ -703,6 +704,11 @@ static int pf_detect(void)
printk("%s: %s version %s, major %d, cluster %d, nice %d\n",
name, name, PF_VERSION, major, cluster, nice);
par_drv = pi_register_driver(name);
if (!par_drv) {
pr_err("failed to register %s driver\n", name);
return -1;
}
k = 0;
if (pf_drive_count == 0) {
if (pi_init(pf->pi, 1, -1, -1, -1, -1, -1, pf_scratch, PI_PF,
@ -735,6 +741,7 @@ static int pf_detect(void)
printk("%s: No ATAPI disk detected\n", name);
for (pf = units, unit = 0; unit < PF_UNITS; pf++, unit++)
put_disk(pf->disk);
pi_unregister_driver(par_drv);
return -1;
}

View File

@ -227,6 +227,7 @@ static int pg_identify(struct pg *dev, int log);
static char pg_scratch[512]; /* scratch block buffer */
static struct class *pg_class;
static void *par_drv; /* reference of parport driver */
/* kernel glue structures */
@ -481,6 +482,12 @@ static int pg_detect(void)
printk("%s: %s version %s, major %d\n", name, name, PG_VERSION, major);
par_drv = pi_register_driver(name);
if (!par_drv) {
pr_err("failed to register %s driver\n", name);
return -1;
}
k = 0;
if (pg_drive_count == 0) {
if (pi_init(dev->pi, 1, -1, -1, -1, -1, -1, pg_scratch,
@ -511,6 +518,7 @@ static int pg_detect(void)
if (k)
return 0;
pi_unregister_driver(par_drv);
printk("%s: No ATAPI device detected\n", name);
return -1;
}

View File

@ -232,6 +232,7 @@ static int pt_identify(struct pt_unit *tape);
static struct pt_unit pt[PT_UNITS];
static char pt_scratch[512]; /* scratch block buffer */
static void *par_drv; /* reference of parport driver */
/* kernel glue structures */
@ -605,6 +606,12 @@ static int pt_detect(void)
printk("%s: %s version %s, major %d\n", name, name, PT_VERSION, major);
par_drv = pi_register_driver(name);
if (!par_drv) {
pr_err("failed to register %s driver\n", name);
return -1;
}
specified = 0;
for (unit = 0; unit < PT_UNITS; unit++) {
struct pt_unit *tape = &pt[unit];
@ -644,6 +651,7 @@ static int pt_detect(void)
if (found)
return 0;
pi_unregister_driver(par_drv);
printk("%s: No ATAPI tape drive detected\n", name);
return -1;
}

View File

@ -590,14 +590,6 @@ config DEVPORT
source "drivers/s390/char/Kconfig"
config MSM_SMD_PKT
bool "Enable device interface for some SMD packet ports"
default n
depends on MSM_SMD
help
Enables userspace clients to read and write to some packet SMD
ports via device interface for MSM chipset.
config TILE_SROM
bool "Character-device access via hypervisor to the Tilera SPI ROM"
depends on TILE

View File

@ -9,7 +9,6 @@ obj-$(CONFIG_ATARI_DSP56K) += dsp56k.o
obj-$(CONFIG_VIRTIO_CONSOLE) += virtio_console.o
obj-$(CONFIG_RAW_DRIVER) += raw.o
obj-$(CONFIG_SGI_SNSC) += snsc.o snsc_event.o
obj-$(CONFIG_MSM_SMD_PKT) += msm_smd_pkt.o
obj-$(CONFIG_MSPEC) += mspec.o
obj-$(CONFIG_MMTIMER) += mmtimer.o
obj-$(CONFIG_UV_MMTIMER) += uv_mmtimer.o
@ -36,7 +35,6 @@ else
obj-$(CONFIG_NVRAM) += nvram.o
endif
obj-$(CONFIG_TOSHIBA) += toshiba.o
obj-$(CONFIG_I8K) += i8k.o
obj-$(CONFIG_DS1620) += ds1620.o
obj-$(CONFIG_HW_RANDOM) += hw_random/
obj-$(CONFIG_PPDEV) += ppdev.o

View File

@ -117,14 +117,14 @@ static int misc_open(struct inode * inode, struct file * file)
const struct file_operations *new_fops = NULL;
mutex_lock(&misc_mtx);
list_for_each_entry(c, &misc_list, list) {
if (c->minor == minor) {
new_fops = fops_get(c->fops);
new_fops = fops_get(c->fops);
break;
}
}
if (!new_fops) {
mutex_unlock(&misc_mtx);
request_module("char-major-%d-%d", MISC_MAJOR, minor);
@ -167,7 +167,7 @@ static const struct file_operations misc_fops = {
/**
* misc_register - register a miscellaneous device
* @misc: device structure
*
*
* Register a miscellaneous device with the kernel. If the minor
* number is set to %MISC_DYNAMIC_MINOR a minor number is assigned
* and placed in the minor field of the structure. For other cases
@ -181,17 +181,18 @@ static const struct file_operations misc_fops = {
* A zero is returned on success and a negative errno code for
* failure.
*/
int misc_register(struct miscdevice * misc)
{
dev_t dev;
int err = 0;
bool is_dynamic = (misc->minor == MISC_DYNAMIC_MINOR);
INIT_LIST_HEAD(&misc->list);
mutex_lock(&misc_mtx);
if (misc->minor == MISC_DYNAMIC_MINOR) {
if (is_dynamic) {
int i = find_first_zero_bit(misc_minors, DYNAMIC_MINORS);
if (i >= DYNAMIC_MINORS) {
err = -EBUSY;
@ -216,9 +217,13 @@ int misc_register(struct miscdevice * misc)
device_create_with_groups(misc_class, misc->parent, dev,
misc, misc->groups, "%s", misc->name);
if (IS_ERR(misc->this_device)) {
int i = DYNAMIC_MINORS - misc->minor - 1;
if (i < DYNAMIC_MINORS && i >= 0)
clear_bit(i, misc_minors);
if (is_dynamic) {
int i = DYNAMIC_MINORS - misc->minor - 1;
if (i < DYNAMIC_MINORS && i >= 0)
clear_bit(i, misc_minors);
misc->minor = MISC_DYNAMIC_MINOR;
}
err = PTR_ERR(misc->this_device);
goto out;
}

View File

@ -1,465 +0,0 @@
/* Copyright (c) 2008-2010, Code Aurora Forum. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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.
*
*/
/*
* SMD Packet Driver -- Provides userspace interface to SMD packet ports.
*/
#include <linux/slab.h>
#include <linux/cdev.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/sched.h>
#include <linux/mutex.h>
#include <linux/delay.h>
#include <linux/uaccess.h>
#include <linux/workqueue.h>
#include <linux/poll.h>
#include <mach/msm_smd.h>
#define NUM_SMD_PKT_PORTS 9
#define DEVICE_NAME "smdpkt"
#define MAX_BUF_SIZE 2048
struct smd_pkt_dev {
struct cdev cdev;
struct device *devicep;
struct smd_channel *ch;
int open_count;
struct mutex ch_lock;
struct mutex rx_lock;
struct mutex tx_lock;
wait_queue_head_t ch_read_wait_queue;
wait_queue_head_t ch_opened_wait_queue;
int i;
unsigned char tx_buf[MAX_BUF_SIZE];
unsigned char rx_buf[MAX_BUF_SIZE];
int remote_open;
} *smd_pkt_devp[NUM_SMD_PKT_PORTS];
struct class *smd_pkt_classp;
static dev_t smd_pkt_number;
static int msm_smd_pkt_debug_enable;
module_param_named(debug_enable, msm_smd_pkt_debug_enable,
int, S_IRUGO | S_IWUSR | S_IWGRP);
#ifdef DEBUG
#define D_DUMP_BUFFER(prestr, cnt, buf) do { \
int i; \
if (msm_smd_pkt_debug_enable) { \
pr_debug("%s", prestr); \
for (i = 0; i < cnt; i++) \
pr_debug("%.2x", buf[i]); \
pr_debug("\n"); \
} \
} while (0)
#else
#define D_DUMP_BUFFER(prestr, cnt, buf) do {} while (0)
#endif
#ifdef DEBUG
#define DBG(x...) do { \
if (msm_smd_pkt_debug_enable) \
pr_debug(x); \
} while (0)
#else
#define DBG(x...) do {} while (0)
#endif
static void check_and_wakeup_reader(struct smd_pkt_dev *smd_pkt_devp)
{
int sz;
if (!smd_pkt_devp || !smd_pkt_devp->ch)
return;
sz = smd_cur_packet_size(smd_pkt_devp->ch);
if (sz == 0) {
DBG("no packet\n");
return;
}
if (sz > smd_read_avail(smd_pkt_devp->ch)) {
DBG("incomplete packet\n");
return;
}
DBG("waking up reader\n");
wake_up_interruptible(&smd_pkt_devp->ch_read_wait_queue);
}
static int smd_pkt_read(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
int r, bytes_read;
struct smd_pkt_dev *smd_pkt_devp;
struct smd_channel *chl;
DBG("read %d bytes\n", count);
if (count > MAX_BUF_SIZE)
return -EINVAL;
smd_pkt_devp = file->private_data;
if (!smd_pkt_devp || !smd_pkt_devp->ch)
return -EINVAL;
chl = smd_pkt_devp->ch;
wait_for_packet:
r = wait_event_interruptible(smd_pkt_devp->ch_read_wait_queue,
(smd_cur_packet_size(chl) > 0 &&
smd_read_avail(chl) >=
smd_cur_packet_size(chl)));
if (r < 0) {
if (r != -ERESTARTSYS)
pr_err("wait returned %d\n", r);
return r;
}
mutex_lock(&smd_pkt_devp->rx_lock);
bytes_read = smd_cur_packet_size(smd_pkt_devp->ch);
if (bytes_read == 0 ||
bytes_read < smd_read_avail(smd_pkt_devp->ch)) {
mutex_unlock(&smd_pkt_devp->rx_lock);
DBG("Nothing to read\n");
goto wait_for_packet;
}
if (bytes_read > count) {
mutex_unlock(&smd_pkt_devp->rx_lock);
pr_info("packet size %d > buffer size %d", bytes_read, count);
return -EINVAL;
}
r = smd_read(smd_pkt_devp->ch, smd_pkt_devp->rx_buf, bytes_read);
if (r != bytes_read) {
mutex_unlock(&smd_pkt_devp->rx_lock);
pr_err("smd_read failed to read %d bytes: %d\n", bytes_read, r);
return -EIO;
}
D_DUMP_BUFFER("read: ", bytes_read, smd_pkt_devp->rx_buf);
r = copy_to_user(buf, smd_pkt_devp->rx_buf, bytes_read);
mutex_unlock(&smd_pkt_devp->rx_lock);
if (r) {
pr_err("copy_to_user failed %d\n", r);
return -EFAULT;
}
DBG("read complete %d bytes\n", bytes_read);
check_and_wakeup_reader(smd_pkt_devp);
return bytes_read;
}
static int smd_pkt_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
int r;
struct smd_pkt_dev *smd_pkt_devp;
if (count > MAX_BUF_SIZE)
return -EINVAL;
DBG("writing %d bytes\n", count);
smd_pkt_devp = file->private_data;
if (!smd_pkt_devp || !smd_pkt_devp->ch)
return -EINVAL;
mutex_lock(&smd_pkt_devp->tx_lock);
if (smd_write_avail(smd_pkt_devp->ch) < count) {
mutex_unlock(&smd_pkt_devp->tx_lock);
DBG("Not enough space to write\n");
return -ENOMEM;
}
D_DUMP_BUFFER("write: ", count, buf);
r = copy_from_user(smd_pkt_devp->tx_buf, buf, count);
if (r) {
mutex_unlock(&smd_pkt_devp->tx_lock);
pr_err("copy_from_user failed %d\n", r);
return -EFAULT;
}
r = smd_write(smd_pkt_devp->ch, smd_pkt_devp->tx_buf, count);
if (r != count) {
mutex_unlock(&smd_pkt_devp->tx_lock);
pr_err("smd_write failed to write %d bytes: %d.\n", count, r);
return -EIO;
}
mutex_unlock(&smd_pkt_devp->tx_lock);
DBG("wrote %d bytes\n", count);
return count;
}
static unsigned int smd_pkt_poll(struct file *file, poll_table *wait)
{
struct smd_pkt_dev *smd_pkt_devp;
unsigned int mask = 0;
smd_pkt_devp = file->private_data;
if (!smd_pkt_devp)
return POLLERR;
DBG("poll waiting\n");
poll_wait(file, &smd_pkt_devp->ch_read_wait_queue, wait);
if (smd_read_avail(smd_pkt_devp->ch))
mask |= POLLIN | POLLRDNORM;
DBG("poll return\n");
return mask;
}
static void smd_pkt_ch_notify(void *priv, unsigned event)
{
struct smd_pkt_dev *smd_pkt_devp = priv;
if (smd_pkt_devp->ch == 0)
return;
switch (event) {
case SMD_EVENT_DATA:
DBG("data\n");
check_and_wakeup_reader(smd_pkt_devp);
break;
case SMD_EVENT_OPEN:
DBG("remote open\n");
smd_pkt_devp->remote_open = 1;
wake_up_interruptible(&smd_pkt_devp->ch_opened_wait_queue);
break;
case SMD_EVENT_CLOSE:
smd_pkt_devp->remote_open = 0;
pr_info("remote closed\n");
break;
default:
pr_err("unknown event %d\n", event);
break;
}
}
static char *smd_pkt_dev_name[] = {
"smdcntl0",
"smdcntl1",
"smdcntl2",
"smdcntl3",
"smdcntl4",
"smdcntl5",
"smdcntl6",
"smdcntl7",
"smd22",
};
static char *smd_ch_name[] = {
"DATA5_CNTL",
"DATA6_CNTL",
"DATA7_CNTL",
"DATA8_CNTL",
"DATA9_CNTL",
"DATA12_CNTL",
"DATA13_CNTL",
"DATA14_CNTL",
"DATA22",
};
static int smd_pkt_open(struct inode *inode, struct file *file)
{
int r = 0;
struct smd_pkt_dev *smd_pkt_devp;
smd_pkt_devp = container_of(inode->i_cdev, struct smd_pkt_dev, cdev);
if (!smd_pkt_devp)
return -EINVAL;
file->private_data = smd_pkt_devp;
mutex_lock(&smd_pkt_devp->ch_lock);
if (smd_pkt_devp->open_count == 0) {
r = smd_open(smd_ch_name[smd_pkt_devp->i],
&smd_pkt_devp->ch, smd_pkt_devp,
smd_pkt_ch_notify);
if (r < 0) {
pr_err("smd_open failed for %s, %d\n",
smd_ch_name[smd_pkt_devp->i], r);
goto out;
}
r = wait_event_interruptible_timeout(
smd_pkt_devp->ch_opened_wait_queue,
smd_pkt_devp->remote_open,
msecs_to_jiffies(2 * HZ));
if (r == 0)
r = -ETIMEDOUT;
if (r < 0) {
pr_err("wait returned %d\n", r);
smd_close(smd_pkt_devp->ch);
smd_pkt_devp->ch = 0;
} else {
smd_pkt_devp->open_count++;
r = 0;
}
}
out:
mutex_unlock(&smd_pkt_devp->ch_lock);
return r;
}
static int smd_pkt_release(struct inode *inode, struct file *file)
{
int r = 0;
struct smd_pkt_dev *smd_pkt_devp = file->private_data;
if (!smd_pkt_devp)
return -EINVAL;
mutex_lock(&smd_pkt_devp->ch_lock);
if (--smd_pkt_devp->open_count == 0) {
r = smd_close(smd_pkt_devp->ch);
smd_pkt_devp->ch = 0;
}
mutex_unlock(&smd_pkt_devp->ch_lock);
return r;
}
static const struct file_operations smd_pkt_fops = {
.owner = THIS_MODULE,
.open = smd_pkt_open,
.release = smd_pkt_release,
.read = smd_pkt_read,
.write = smd_pkt_write,
.poll = smd_pkt_poll,
};
static int __init smd_pkt_init(void)
{
int i;
int r;
r = alloc_chrdev_region(&smd_pkt_number, 0,
NUM_SMD_PKT_PORTS, DEVICE_NAME);
if (r) {
pr_err("alloc_chrdev_region() failed %d\n", r);
return r;
}
smd_pkt_classp = class_create(THIS_MODULE, DEVICE_NAME);
if (IS_ERR(smd_pkt_classp)) {
r = PTR_ERR(smd_pkt_classp);
pr_err("class_create() failed %d\n", r);
goto unreg_chardev;
}
for (i = 0; i < NUM_SMD_PKT_PORTS; ++i) {
smd_pkt_devp[i] = kzalloc(sizeof(struct smd_pkt_dev),
GFP_KERNEL);
if (!smd_pkt_devp[i]) {
pr_err("kmalloc() failed\n");
goto clean_cdevs;
}
smd_pkt_devp[i]->i = i;
init_waitqueue_head(&smd_pkt_devp[i]->ch_read_wait_queue);
smd_pkt_devp[i]->remote_open = 0;
init_waitqueue_head(&smd_pkt_devp[i]->ch_opened_wait_queue);
mutex_init(&smd_pkt_devp[i]->ch_lock);
mutex_init(&smd_pkt_devp[i]->rx_lock);
mutex_init(&smd_pkt_devp[i]->tx_lock);
cdev_init(&smd_pkt_devp[i]->cdev, &smd_pkt_fops);
smd_pkt_devp[i]->cdev.owner = THIS_MODULE;
r = cdev_add(&smd_pkt_devp[i]->cdev,
(smd_pkt_number + i), 1);
if (r) {
pr_err("cdev_add() failed %d\n", r);
kfree(smd_pkt_devp[i]);
goto clean_cdevs;
}
smd_pkt_devp[i]->devicep =
device_create(smd_pkt_classp, NULL,
(smd_pkt_number + i), NULL,
smd_pkt_dev_name[i]);
if (IS_ERR(smd_pkt_devp[i]->devicep)) {
r = PTR_ERR(smd_pkt_devp[i]->devicep);
pr_err("device_create() failed %d\n", r);
cdev_del(&smd_pkt_devp[i]->cdev);
kfree(smd_pkt_devp[i]);
goto clean_cdevs;
}
}
pr_info("SMD Packet Port Driver Initialized.\n");
return 0;
clean_cdevs:
if (i > 0) {
while (--i >= 0) {
mutex_destroy(&smd_pkt_devp[i]->ch_lock);
mutex_destroy(&smd_pkt_devp[i]->rx_lock);
mutex_destroy(&smd_pkt_devp[i]->tx_lock);
cdev_del(&smd_pkt_devp[i]->cdev);
kfree(smd_pkt_devp[i]);
device_destroy(smd_pkt_classp,
MKDEV(MAJOR(smd_pkt_number), i));
}
}
class_destroy(smd_pkt_classp);
unreg_chardev:
unregister_chrdev_region(MAJOR(smd_pkt_number), NUM_SMD_PKT_PORTS);
return r;
}
module_init(smd_pkt_init);
static void __exit smd_pkt_cleanup(void)
{
int i;
for (i = 0; i < NUM_SMD_PKT_PORTS; ++i) {
mutex_destroy(&smd_pkt_devp[i]->ch_lock);
mutex_destroy(&smd_pkt_devp[i]->rx_lock);
mutex_destroy(&smd_pkt_devp[i]->tx_lock);
cdev_del(&smd_pkt_devp[i]->cdev);
kfree(smd_pkt_devp[i]);
device_destroy(smd_pkt_classp,
MKDEV(MAJOR(smd_pkt_number), i));
}
class_destroy(smd_pkt_classp);
unregister_chrdev_region(MAJOR(smd_pkt_number), NUM_SMD_PKT_PORTS);
}
module_exit(smd_pkt_cleanup);
MODULE_DESCRIPTION("MSM Shared Memory Packet Port");
MODULE_LICENSE("GPL v2");

View File

@ -437,7 +437,7 @@ static int mgslpc_device_count = 0;
* .text section address and breakpoint on module load.
* This is useful for use with gdb and add-symbol-file command.
*/
static bool break_on_load=0;
static bool break_on_load;
/*
* Driver major number, defaults to zero to get auto

View File

@ -198,7 +198,7 @@ scdrv_read(struct file *file, char __user *buf, size_t count, loff_t *f_pos)
add_wait_queue(&sd->sd_rq, &wait);
spin_unlock_irqrestore(&sd->sd_rlock, flags);
schedule_timeout(SCDRV_TIMEOUT);
schedule_timeout(msecs_to_jiffies(SCDRV_TIMEOUT));
remove_wait_queue(&sd->sd_rq, &wait);
if (signal_pending(current)) {
@ -294,7 +294,7 @@ scdrv_write(struct file *file, const char __user *buf,
add_wait_queue(&sd->sd_wq, &wait);
spin_unlock_irqrestore(&sd->sd_wlock, flags);
schedule_timeout(SCDRV_TIMEOUT);
schedule_timeout(msecs_to_jiffies(SCDRV_TIMEOUT));
remove_wait_queue(&sd->sd_wq, &wait);
if (signal_pending(current)) {

View File

@ -1492,8 +1492,8 @@ static int add_port(struct ports_device *portdev, u32 id)
* Finally, create the debugfs file that we can use to
* inspect a port's state at any time
*/
sprintf(debugfs_name, "vport%up%u",
port->portdev->vdev->index, id);
snprintf(debugfs_name, sizeof(debugfs_name), "vport%up%u",
port->portdev->vdev->index, id);
port->debugfs_file = debugfs_create_file(debugfs_name, 0444,
pdrvdata.debugfs_dir,
port,

View File

@ -270,7 +270,7 @@ int buffer_icap_set_configuration(struct hwicap_drvdata *drvdata, u32 *data,
int status;
s32 buffer_count = 0;
s32 num_writes = 0;
bool dirty = 0;
bool dirty = false;
u32 i;
void __iomem *base_address = drvdata->base_address;
@ -279,7 +279,7 @@ int buffer_icap_set_configuration(struct hwicap_drvdata *drvdata, u32 *data,
/* Copy data to bram */
buffer_icap_set_bram(base_address, buffer_count, data[i]);
dirty = 1;
dirty = true;
if (buffer_count < XHI_MAX_BUFFER_INTS - 1) {
buffer_count++;
@ -299,7 +299,7 @@ int buffer_icap_set_configuration(struct hwicap_drvdata *drvdata, u32 *data,
buffer_count = 0;
num_writes++;
dirty = 0;
dirty = false;
}
/* Write unwritten data to ICAP */

View File

@ -24,7 +24,7 @@ config XILLYBUS_PCIE
config XILLYBUS_OF
tristate "Xillybus over Device Tree"
depends on OF_ADDRESS && OF_IRQ
depends on OF_ADDRESS && OF_IRQ && HAS_DMA
help
Set to M if you want Xillybus to find its resources from the
Open Firmware Flattened Device Tree. If the target is an embedded

View File

@ -28,15 +28,22 @@ config EXTCON_ARIZONA
with Wolfson Arizona devices. These are audio CODECs with
advanced audio accessory detection support.
config EXTCON_AXP288
tristate "X-Power AXP288 EXTCON support"
depends on MFD_AXP20X && USB_PHY
help
Say Y here to enable support for USB peripheral detection
and USB MUX switching by X-Power AXP288 PMIC.
config EXTCON_GPIO
tristate "GPIO extcon support"
depends on GPIOLIB
depends on GPIOLIB || COMPILE_TEST
help
Say Y here to enable GPIO based extcon support. Note that GPIO
extcon supports single state per extcon instance.
config EXTCON_MAX14577
tristate "MAX14577/77836 EXTCON Support"
tristate "Maxim MAX14577/77836 EXTCON Support"
depends on MFD_MAX14577
select IRQ_DOMAIN
select REGMAP_I2C
@ -46,7 +53,7 @@ config EXTCON_MAX14577
detector and switch.
config EXTCON_MAX77693
tristate "MAX77693 EXTCON Support"
tristate "Maxim MAX77693 EXTCON Support"
depends on MFD_MAX77693 && INPUT
select IRQ_DOMAIN
select REGMAP_I2C
@ -56,7 +63,7 @@ config EXTCON_MAX77693
detector and switch.
config EXTCON_MAX77843
tristate "MAX77843 EXTCON Support"
tristate "Maxim MAX77843 EXTCON Support"
depends on MFD_MAX77843
select IRQ_DOMAIN
select REGMAP_I2C
@ -66,7 +73,7 @@ config EXTCON_MAX77843
detector add switch.
config EXTCON_MAX8997
tristate "MAX8997 EXTCON Support"
tristate "Maxim MAX8997 EXTCON Support"
depends on MFD_MAX8997 && IRQ_DOMAIN
help
If you say yes here you get support for the MUIC device of
@ -81,7 +88,7 @@ config EXTCON_PALMAS
detection by palmas usb.
config EXTCON_RT8973A
tristate "RT8973A EXTCON support"
tristate "Richtek RT8973A EXTCON support"
depends on I2C
select IRQ_DOMAIN
select REGMAP_I2C
@ -93,7 +100,7 @@ config EXTCON_RT8973A
from abnormal high input voltage (up to 28V).
config EXTCON_SM5502
tristate "SM5502 EXTCON support"
tristate "Silicon Mitus SM5502 EXTCON support"
depends on I2C
select IRQ_DOMAIN
select REGMAP_I2C
@ -105,9 +112,9 @@ config EXTCON_SM5502
config EXTCON_USB_GPIO
tristate "USB GPIO extcon support"
depends on GPIOLIB
depends on GPIOLIB || COMPILE_TEST
help
Say Y here to enable GPIO based USB cable detection extcon support.
Used typically if GPIO is used for USB ID pin detection.
endif # MULTISTATE_SWITCH
endif

View File

@ -5,6 +5,7 @@
obj-$(CONFIG_EXTCON) += extcon.o
obj-$(CONFIG_EXTCON_ADC_JACK) += extcon-adc-jack.o
obj-$(CONFIG_EXTCON_ARIZONA) += extcon-arizona.o
obj-$(CONFIG_EXTCON_AXP288) += extcon-axp288.o
obj-$(CONFIG_EXTCON_GPIO) += extcon-gpio.o
obj-$(CONFIG_EXTCON_MAX14577) += extcon-max14577.o
obj-$(CONFIG_EXTCON_MAX77693) += extcon-max77693.o

View File

@ -29,7 +29,6 @@
* struct adc_jack_data - internal data for adc_jack device driver
* @edev: extcon device.
* @cable_names: list of supported cables.
* @num_cables: size of cable_names.
* @adc_conditions: list of adc value conditions.
* @num_conditions: size of adc_conditions.
* @irq: irq number of attach/detach event (0 if not exist).
@ -41,8 +40,7 @@
struct adc_jack_data {
struct extcon_dev *edev;
const char **cable_names;
int num_cables;
const unsigned int **cable_names;
struct adc_jack_cond *adc_conditions;
int num_conditions;
@ -112,17 +110,6 @@ static int adc_jack_probe(struct platform_device *pdev)
dev_err(&pdev->dev, "failed to allocate extcon device\n");
return -ENOMEM;
}
data->edev->name = pdata->name;
/* Check the length of array and set num_cables */
for (i = 0; data->edev->supported_cable[i]; i++)
;
if (i == 0 || i > SUPPORTED_CABLE_MAX) {
dev_err(&pdev->dev, "error: pdata->cable_names size = %d\n",
i - 1);
return -EINVAL;
}
data->num_cables = i;
if (!pdata->adc_conditions ||
!pdata->adc_conditions[0].state) {

View File

@ -32,13 +32,10 @@
#include <linux/mfd/arizona/core.h>
#include <linux/mfd/arizona/pdata.h>
#include <linux/mfd/arizona/registers.h>
#include <dt-bindings/mfd/arizona.h>
#define ARIZONA_MAX_MICD_RANGE 8
#define ARIZONA_ACCDET_MODE_MIC 0
#define ARIZONA_ACCDET_MODE_HPL 1
#define ARIZONA_ACCDET_MODE_HPR 2
#define ARIZONA_MICD_CLAMP_MODE_JDL 0x4
#define ARIZONA_MICD_CLAMP_MODE_JDH 0x5
#define ARIZONA_MICD_CLAMP_MODE_JDL_GP5H 0x9
@ -94,7 +91,7 @@ struct arizona_extcon_info {
bool detecting;
int jack_flips;
int hpdet_ip;
int hpdet_ip_version;
struct extcon_dev *edev;
};
@ -121,17 +118,12 @@ static const int arizona_micd_levels[] = {
1257,
};
#define ARIZONA_CABLE_MECHANICAL 0
#define ARIZONA_CABLE_MICROPHONE 1
#define ARIZONA_CABLE_HEADPHONE 2
#define ARIZONA_CABLE_LINEOUT 3
static const char *arizona_cable[] = {
"Mechanical",
"Microphone",
"Headphone",
"Line-out",
NULL,
static const unsigned int arizona_cable[] = {
EXTCON_MECHANICAL,
EXTCON_MICROPHONE,
EXTCON_HEADPHONE,
EXTCON_LINE_OUT,
EXTCON_NONE,
};
static void arizona_start_hpdet_acc_id(struct arizona_extcon_info *info);
@ -145,6 +137,7 @@ static void arizona_extcon_hp_clamp(struct arizona_extcon_info *info,
switch (arizona->type) {
case WM5110:
case WM8280:
mask = ARIZONA_HP1L_SHRTO | ARIZONA_HP1L_FLWR |
ARIZONA_HP1L_SHRTI;
if (clamp)
@ -380,7 +373,7 @@ static int arizona_hpdet_read(struct arizona_extcon_info *info)
return ret;
}
switch (info->hpdet_ip) {
switch (info->hpdet_ip_version) {
case 0:
if (!(val & ARIZONA_HP_DONE)) {
dev_err(arizona->dev, "HPDET did not complete: %x\n",
@ -441,7 +434,7 @@ static int arizona_hpdet_read(struct arizona_extcon_info *info)
default:
dev_warn(arizona->dev, "Unknown HPDET IP revision %d\n",
info->hpdet_ip);
info->hpdet_ip_version);
case 2:
if (!(val & ARIZONA_HP_DONE_B)) {
dev_err(arizona->dev, "HPDET did not complete: %x\n",
@ -559,7 +552,7 @@ static irqreturn_t arizona_hpdet_irq(int irq, void *data)
struct arizona_extcon_info *info = data;
struct arizona *arizona = info->arizona;
int id_gpio = arizona->pdata.hpdet_id_gpio;
int report = ARIZONA_CABLE_HEADPHONE;
unsigned int report = EXTCON_HEADPHONE;
int ret, reading;
bool mic = false;
@ -573,7 +566,7 @@ static irqreturn_t arizona_hpdet_irq(int irq, void *data)
}
/* If the cable was removed while measuring ignore the result */
ret = extcon_get_cable_state_(info->edev, ARIZONA_CABLE_MECHANICAL);
ret = extcon_get_cable_state_(info->edev, EXTCON_MECHANICAL);
if (ret < 0) {
dev_err(arizona->dev, "Failed to check cable state: %d\n",
ret);
@ -604,9 +597,9 @@ static irqreturn_t arizona_hpdet_irq(int irq, void *data)
/* Report high impedence cables as line outputs */
if (reading >= 5000)
report = ARIZONA_CABLE_LINEOUT;
report = EXTCON_LINE_OUT;
else
report = ARIZONA_CABLE_HEADPHONE;
report = EXTCON_HEADPHONE;
ret = extcon_set_cable_state_(info->edev, report, true);
if (ret != 0)
@ -670,9 +663,9 @@ static void arizona_identify_headphone(struct arizona_extcon_info *info)
ret = regmap_update_bits(arizona->regmap,
ARIZONA_ACCESSORY_DETECT_MODE_1,
ARIZONA_ACCDET_MODE_MASK,
ARIZONA_ACCDET_MODE_HPL);
arizona->pdata.hpdet_channel);
if (ret != 0) {
dev_err(arizona->dev, "Failed to set HPDETL mode: %d\n", ret);
dev_err(arizona->dev, "Failed to set HPDET mode: %d\n", ret);
goto err;
}
@ -691,8 +684,7 @@ err:
ARIZONA_ACCDET_MODE_MASK, ARIZONA_ACCDET_MODE_MIC);
/* Just report headphone */
ret = extcon_set_cable_state_(info->edev,
ARIZONA_CABLE_HEADPHONE, true);
ret = extcon_set_cable_state_(info->edev, EXTCON_HEADPHONE, true);
if (ret != 0)
dev_err(arizona->dev, "Failed to report headphone: %d\n", ret);
@ -722,9 +714,9 @@ static void arizona_start_hpdet_acc_id(struct arizona_extcon_info *info)
ARIZONA_ACCESSORY_DETECT_MODE_1,
ARIZONA_ACCDET_SRC | ARIZONA_ACCDET_MODE_MASK,
info->micd_modes[0].src |
ARIZONA_ACCDET_MODE_HPL);
arizona->pdata.hpdet_channel);
if (ret != 0) {
dev_err(arizona->dev, "Failed to set HPDETL mode: %d\n", ret);
dev_err(arizona->dev, "Failed to set HPDET mode: %d\n", ret);
goto err;
}
@ -749,8 +741,7 @@ err:
ARIZONA_ACCDET_MODE_MASK, ARIZONA_ACCDET_MODE_MIC);
/* Just report headphone */
ret = extcon_set_cable_state_(info->edev,
ARIZONA_CABLE_HEADPHONE, true);
ret = extcon_set_cable_state_(info->edev, EXTCON_HEADPHONE, true);
if (ret != 0)
dev_err(arizona->dev, "Failed to report headphone: %d\n", ret);
@ -789,7 +780,7 @@ static void arizona_micd_detect(struct work_struct *work)
mutex_lock(&info->lock);
/* If the cable was removed while measuring ignore the result */
ret = extcon_get_cable_state_(info->edev, ARIZONA_CABLE_MECHANICAL);
ret = extcon_get_cable_state_(info->edev, EXTCON_MECHANICAL);
if (ret < 0) {
dev_err(arizona->dev, "Failed to check cable state: %d\n",
ret);
@ -838,8 +829,7 @@ static void arizona_micd_detect(struct work_struct *work)
arizona_identify_headphone(info);
ret = extcon_set_cable_state_(info->edev,
ARIZONA_CABLE_MICROPHONE, true);
EXTCON_MICROPHONE, true);
if (ret != 0)
dev_err(arizona->dev, "Headset report failed: %d\n",
ret);
@ -1030,7 +1020,7 @@ static irqreturn_t arizona_jackdet(int irq, void *data)
if (info->last_jackdet == present) {
dev_dbg(arizona->dev, "Detected jack\n");
ret = extcon_set_cable_state_(info->edev,
ARIZONA_CABLE_MECHANICAL, true);
EXTCON_MECHANICAL, true);
if (ret != 0)
dev_err(arizona->dev, "Mechanical report failed: %d\n",
@ -1120,6 +1110,26 @@ static void arizona_micd_set_level(struct arizona *arizona, int index,
regmap_update_bits(arizona->regmap, reg, mask, level);
}
static int arizona_extcon_of_get_pdata(struct arizona *arizona)
{
struct arizona_pdata *pdata = &arizona->pdata;
unsigned int val = ARIZONA_ACCDET_MODE_HPL;
of_property_read_u32(arizona->dev->of_node, "wlf,hpdet-channel", &val);
switch (val) {
case ARIZONA_ACCDET_MODE_HPL:
case ARIZONA_ACCDET_MODE_HPR:
pdata->hpdet_channel = val;
break;
default:
dev_err(arizona->dev,
"Wrong wlf,hpdet-channel DT value %d\n", val);
pdata->hpdet_channel = ARIZONA_ACCDET_MODE_HPL;
}
return 0;
}
static int arizona_extcon_probe(struct platform_device *pdev)
{
struct arizona *arizona = dev_get_drvdata(pdev->dev.parent);
@ -1137,6 +1147,11 @@ static int arizona_extcon_probe(struct platform_device *pdev)
if (!info)
return -ENOMEM;
if (IS_ENABLED(CONFIG_OF)) {
if (!dev_get_platdata(arizona->dev))
arizona_extcon_of_get_pdata(arizona);
}
info->micvdd = devm_regulator_get(&pdev->dev, "MICVDD");
if (IS_ERR(info->micvdd)) {
ret = PTR_ERR(info->micvdd);
@ -1161,7 +1176,7 @@ static int arizona_extcon_probe(struct platform_device *pdev)
break;
default:
info->micd_clamp = true;
info->hpdet_ip = 1;
info->hpdet_ip_version = 1;
break;
}
break;
@ -1172,7 +1187,7 @@ static int arizona_extcon_probe(struct platform_device *pdev)
break;
default:
info->micd_clamp = true;
info->hpdet_ip = 2;
info->hpdet_ip_version = 2;
break;
}
break;
@ -1185,7 +1200,6 @@ static int arizona_extcon_probe(struct platform_device *pdev)
dev_err(&pdev->dev, "failed to allocate extcon device\n");
return -ENOMEM;
}
info->edev->name = "Headset Jack";
ret = devm_extcon_dev_register(&pdev->dev, info->edev);
if (ret < 0) {

View File

@ -0,0 +1,381 @@
/*
* extcon-axp288.c - X-Power AXP288 PMIC extcon cable detection driver
*
* Copyright (C) 2015 Intel Corporation
* Author: Ramakrishna Pallala <ramakrishna.pallala@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.
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/usb/phy.h>
#include <linux/notifier.h>
#include <linux/extcon.h>
#include <linux/regmap.h>
#include <linux/gpio.h>
#include <linux/gpio/consumer.h>
#include <linux/mfd/axp20x.h>
/* Power source status register */
#define PS_STAT_VBUS_TRIGGER BIT(0)
#define PS_STAT_BAT_CHRG_DIR BIT(2)
#define PS_STAT_VBUS_ABOVE_VHOLD BIT(3)
#define PS_STAT_VBUS_VALID BIT(4)
#define PS_STAT_VBUS_PRESENT BIT(5)
/* BC module global register */
#define BC_GLOBAL_RUN BIT(0)
#define BC_GLOBAL_DET_STAT BIT(2)
#define BC_GLOBAL_DBP_TOUT BIT(3)
#define BC_GLOBAL_VLGC_COM_SEL BIT(4)
#define BC_GLOBAL_DCD_TOUT_MASK (BIT(6)|BIT(5))
#define BC_GLOBAL_DCD_TOUT_300MS 0
#define BC_GLOBAL_DCD_TOUT_100MS 1
#define BC_GLOBAL_DCD_TOUT_500MS 2
#define BC_GLOBAL_DCD_TOUT_900MS 3
#define BC_GLOBAL_DCD_DET_SEL BIT(7)
/* BC module vbus control and status register */
#define VBUS_CNTL_DPDM_PD_EN BIT(4)
#define VBUS_CNTL_DPDM_FD_EN BIT(5)
#define VBUS_CNTL_FIRST_PO_STAT BIT(6)
/* BC USB status register */
#define USB_STAT_BUS_STAT_MASK (BIT(3)|BIT(2)|BIT(1)|BIT(0))
#define USB_STAT_BUS_STAT_SHIFT 0
#define USB_STAT_BUS_STAT_ATHD 0
#define USB_STAT_BUS_STAT_CONN 1
#define USB_STAT_BUS_STAT_SUSP 2
#define USB_STAT_BUS_STAT_CONF 3
#define USB_STAT_USB_SS_MODE BIT(4)
#define USB_STAT_DEAD_BAT_DET BIT(6)
#define USB_STAT_DBP_UNCFG BIT(7)
/* BC detect status register */
#define DET_STAT_MASK (BIT(7)|BIT(6)|BIT(5))
#define DET_STAT_SHIFT 5
#define DET_STAT_SDP 1
#define DET_STAT_CDP 2
#define DET_STAT_DCP 3
/* IRQ enable-1 register */
#define PWRSRC_IRQ_CFG_MASK (BIT(4)|BIT(3)|BIT(2))
/* IRQ enable-6 register */
#define BC12_IRQ_CFG_MASK BIT(1)
enum axp288_extcon_reg {
AXP288_PS_STAT_REG = 0x00,
AXP288_PS_BOOT_REASON_REG = 0x02,
AXP288_BC_GLOBAL_REG = 0x2c,
AXP288_BC_VBUS_CNTL_REG = 0x2d,
AXP288_BC_USB_STAT_REG = 0x2e,
AXP288_BC_DET_STAT_REG = 0x2f,
AXP288_PWRSRC_IRQ_CFG_REG = 0x40,
AXP288_BC12_IRQ_CFG_REG = 0x45,
};
enum axp288_mux_select {
EXTCON_GPIO_MUX_SEL_PMIC = 0,
EXTCON_GPIO_MUX_SEL_SOC,
};
enum axp288_extcon_irq {
VBUS_FALLING_IRQ = 0,
VBUS_RISING_IRQ,
MV_CHNG_IRQ,
BC_USB_CHNG_IRQ,
EXTCON_IRQ_END,
};
static const unsigned int axp288_extcon_cables[] = {
EXTCON_SLOW_CHARGER,
EXTCON_CHARGE_DOWNSTREAM,
EXTCON_FAST_CHARGER,
EXTCON_NONE,
};
struct axp288_extcon_info {
struct device *dev;
struct regmap *regmap;
struct regmap_irq_chip_data *regmap_irqc;
struct axp288_extcon_pdata *pdata;
int irq[EXTCON_IRQ_END];
struct extcon_dev *edev;
struct notifier_block extcon_nb;
struct usb_phy *otg;
};
/* Power up/down reason string array */
static char *axp288_pwr_up_down_info[] = {
"Last wake caused by user pressing the power button",
"Last wake caused by a charger insertion",
"Last wake caused by a battery insertion",
"Last wake caused by SOC initiated global reset",
"Last wake caused by cold reset",
"Last shutdown caused by PMIC UVLO threshold",
"Last shutdown caused by SOC initiated cold off",
"Last shutdown caused by user pressing the power button",
NULL,
};
/*
* Decode and log the given "reset source indicator" (rsi)
* register and then clear it.
*/
static void axp288_extcon_log_rsi(struct axp288_extcon_info *info)
{
char **rsi;
unsigned int val, i, clear_mask = 0;
int ret;
ret = regmap_read(info->regmap, AXP288_PS_BOOT_REASON_REG, &val);
for (i = 0, rsi = axp288_pwr_up_down_info; *rsi; rsi++, i++) {
if (val & BIT(i)) {
dev_dbg(info->dev, "%s\n", *rsi);
clear_mask |= BIT(i);
}
}
/* Clear the register value for next reboot (write 1 to clear bit) */
regmap_write(info->regmap, AXP288_PS_BOOT_REASON_REG, clear_mask);
}
static int axp288_handle_chrg_det_event(struct axp288_extcon_info *info)
{
static bool notify_otg, notify_charger;
static unsigned int cable;
int ret, stat, cfg, pwr_stat;
u8 chrg_type;
bool vbus_attach = false;
ret = regmap_read(info->regmap, AXP288_PS_STAT_REG, &pwr_stat);
if (ret < 0) {
dev_err(info->dev, "failed to read vbus status\n");
return ret;
}
vbus_attach = (pwr_stat & PS_STAT_VBUS_PRESENT);
if (!vbus_attach)
goto notify_otg;
/* Check charger detection completion status */
ret = regmap_read(info->regmap, AXP288_BC_GLOBAL_REG, &cfg);
if (ret < 0)
goto dev_det_ret;
if (cfg & BC_GLOBAL_DET_STAT) {
dev_dbg(info->dev, "can't complete the charger detection\n");
goto dev_det_ret;
}
ret = regmap_read(info->regmap, AXP288_BC_DET_STAT_REG, &stat);
if (ret < 0)
goto dev_det_ret;
chrg_type = (stat & DET_STAT_MASK) >> DET_STAT_SHIFT;
switch (chrg_type) {
case DET_STAT_SDP:
dev_dbg(info->dev, "sdp cable is connecetd\n");
notify_otg = true;
notify_charger = true;
cable = EXTCON_SLOW_CHARGER;
break;
case DET_STAT_CDP:
dev_dbg(info->dev, "cdp cable is connecetd\n");
notify_otg = true;
notify_charger = true;
cable = EXTCON_CHARGE_DOWNSTREAM;
break;
case DET_STAT_DCP:
dev_dbg(info->dev, "dcp cable is connecetd\n");
notify_charger = true;
cable = EXTCON_FAST_CHARGER;
break;
default:
dev_warn(info->dev,
"disconnect or unknown or ID event\n");
}
notify_otg:
if (notify_otg) {
/*
* If VBUS is absent Connect D+/D- lines to PMIC for BC
* detection. Else connect them to SOC for USB communication.
*/
if (info->pdata->gpio_mux_cntl)
gpiod_set_value(info->pdata->gpio_mux_cntl,
vbus_attach ? EXTCON_GPIO_MUX_SEL_SOC
: EXTCON_GPIO_MUX_SEL_PMIC);
atomic_notifier_call_chain(&info->otg->notifier,
vbus_attach ? USB_EVENT_VBUS : USB_EVENT_NONE, NULL);
}
if (notify_charger)
extcon_set_cable_state_(info->edev, cable, vbus_attach);
/* Clear the flags on disconnect event */
if (!vbus_attach)
notify_otg = notify_charger = false;
return 0;
dev_det_ret:
if (ret < 0)
dev_err(info->dev, "failed to detect BC Mod\n");
return ret;
}
static irqreturn_t axp288_extcon_isr(int irq, void *data)
{
struct axp288_extcon_info *info = data;
int ret;
ret = axp288_handle_chrg_det_event(info);
if (ret < 0)
dev_err(info->dev, "failed to handle the interrupt\n");
return IRQ_HANDLED;
}
static void axp288_extcon_enable_irq(struct axp288_extcon_info *info)
{
/* Unmask VBUS interrupt */
regmap_write(info->regmap, AXP288_PWRSRC_IRQ_CFG_REG,
PWRSRC_IRQ_CFG_MASK);
regmap_update_bits(info->regmap, AXP288_BC_GLOBAL_REG,
BC_GLOBAL_RUN, 0);
/* Unmask the BC1.2 complete interrupts */
regmap_write(info->regmap, AXP288_BC12_IRQ_CFG_REG, BC12_IRQ_CFG_MASK);
/* Enable the charger detection logic */
regmap_update_bits(info->regmap, AXP288_BC_GLOBAL_REG,
BC_GLOBAL_RUN, BC_GLOBAL_RUN);
}
static int axp288_extcon_probe(struct platform_device *pdev)
{
struct axp288_extcon_info *info;
struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent);
int ret, i, pirq, gpio;
info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
if (!info)
return -ENOMEM;
info->dev = &pdev->dev;
info->regmap = axp20x->regmap;
info->regmap_irqc = axp20x->regmap_irqc;
info->pdata = pdev->dev.platform_data;
if (!info->pdata) {
/* Try ACPI provided pdata via device properties */
if (!device_property_present(&pdev->dev,
"axp288_extcon_data\n"))
dev_err(&pdev->dev, "failed to get platform data\n");
return -ENODEV;
}
platform_set_drvdata(pdev, info);
axp288_extcon_log_rsi(info);
/* Initialize extcon device */
info->edev = devm_extcon_dev_allocate(&pdev->dev,
axp288_extcon_cables);
if (IS_ERR(info->edev)) {
dev_err(&pdev->dev, "failed to allocate memory for extcon\n");
return PTR_ERR(info->edev);
}
/* Register extcon device */
ret = devm_extcon_dev_register(&pdev->dev, info->edev);
if (ret) {
dev_err(&pdev->dev, "failed to register extcon device\n");
return ret;
}
/* Get otg transceiver phy */
info->otg = usb_get_phy(USB_PHY_TYPE_USB2);
if (IS_ERR(info->otg)) {
dev_err(&pdev->dev, "failed to get otg transceiver\n");
return PTR_ERR(info->otg);
}
/* Set up gpio control for USB Mux */
if (info->pdata->gpio_mux_cntl) {
gpio = desc_to_gpio(info->pdata->gpio_mux_cntl);
ret = gpio_request(gpio, "USB_MUX");
if (ret < 0) {
dev_err(&pdev->dev,
"failed to request the gpio=%d\n", gpio);
goto gpio_req_failed;
}
gpiod_direction_output(info->pdata->gpio_mux_cntl,
EXTCON_GPIO_MUX_SEL_PMIC);
}
for (i = 0; i < EXTCON_IRQ_END; i++) {
pirq = platform_get_irq(pdev, i);
info->irq[i] = regmap_irq_get_virq(info->regmap_irqc, pirq);
if (info->irq[i] < 0) {
dev_err(&pdev->dev,
"failed to get virtual interrupt=%d\n", pirq);
ret = info->irq[i];
goto gpio_req_failed;
}
ret = devm_request_threaded_irq(&pdev->dev, info->irq[i],
NULL, axp288_extcon_isr,
IRQF_ONESHOT | IRQF_NO_SUSPEND,
pdev->name, info);
if (ret) {
dev_err(&pdev->dev, "failed to request interrupt=%d\n",
info->irq[i]);
goto gpio_req_failed;
}
}
/* Enable interrupts */
axp288_extcon_enable_irq(info);
return 0;
gpio_req_failed:
usb_put_phy(info->otg);
return ret;
}
static int axp288_extcon_remove(struct platform_device *pdev)
{
struct axp288_extcon_info *info = platform_get_drvdata(pdev);
usb_put_phy(info->otg);
return 0;
}
static struct platform_driver axp288_extcon_driver = {
.probe = axp288_extcon_probe,
.remove = axp288_extcon_remove,
.driver = {
.name = "axp288_extcon",
},
};
module_platform_driver(axp288_extcon_driver);
MODULE_AUTHOR("Ramakrishna Pallala <ramakrishna.pallala@intel.com>");
MODULE_DESCRIPTION("X-Powers AXP288 extcon driver");
MODULE_LICENSE("GPL v2");

View File

@ -104,7 +104,6 @@ static int gpio_extcon_probe(struct platform_device *pdev)
dev_err(&pdev->dev, "failed to allocate extcon device\n");
return -ENOMEM;
}
extcon_data->edev->name = pdata->name;
extcon_data->gpio = pdata->gpio;
extcon_data->gpio_active_low = pdata->gpio_active_low;

View File

@ -148,33 +148,14 @@ enum max14577_muic_acc_type {
MAX14577_MUIC_ADC_OPEN,
};
/* max14577 MUIC device support below list of accessories(external connector) */
enum {
EXTCON_CABLE_USB = 0,
EXTCON_CABLE_TA,
EXTCON_CABLE_FAST_CHARGER,
EXTCON_CABLE_SLOW_CHARGER,
EXTCON_CABLE_CHARGE_DOWNSTREAM,
EXTCON_CABLE_JIG_USB_ON,
EXTCON_CABLE_JIG_USB_OFF,
EXTCON_CABLE_JIG_UART_OFF,
EXTCON_CABLE_JIG_UART_ON,
_EXTCON_CABLE_NUM,
};
static const char *max14577_extcon_cable[] = {
[EXTCON_CABLE_USB] = "USB",
[EXTCON_CABLE_TA] = "TA",
[EXTCON_CABLE_FAST_CHARGER] = "Fast-charger",
[EXTCON_CABLE_SLOW_CHARGER] = "Slow-charger",
[EXTCON_CABLE_CHARGE_DOWNSTREAM] = "Charge-downstream",
[EXTCON_CABLE_JIG_USB_ON] = "JIG-USB-ON",
[EXTCON_CABLE_JIG_USB_OFF] = "JIG-USB-OFF",
[EXTCON_CABLE_JIG_UART_OFF] = "JIG-UART-OFF",
[EXTCON_CABLE_JIG_UART_ON] = "JIG-UART-ON",
NULL,
static const unsigned int max14577_extcon_cable[] = {
EXTCON_USB,
EXTCON_TA,
EXTCON_FAST_CHARGER,
EXTCON_SLOW_CHARGER,
EXTCON_CHARGE_DOWNSTREAM,
EXTCON_JIG,
EXTCON_NONE,
};
/*
@ -348,7 +329,6 @@ static int max14577_muic_get_cable_type(struct max14577_muic_info *info,
static int max14577_muic_jig_handler(struct max14577_muic_info *info,
int cable_type, bool attached)
{
char cable_name[32];
int ret = 0;
u8 path = CTRL1_SW_OPEN;
@ -358,18 +338,12 @@ static int max14577_muic_jig_handler(struct max14577_muic_info *info,
switch (cable_type) {
case MAX14577_MUIC_ADC_FACTORY_MODE_USB_OFF: /* ADC_JIG_USB_OFF */
/* PATH:AP_USB */
strcpy(cable_name, "JIG-USB-OFF");
path = CTRL1_SW_USB;
break;
case MAX14577_MUIC_ADC_FACTORY_MODE_USB_ON: /* ADC_JIG_USB_ON */
/* PATH:AP_USB */
strcpy(cable_name, "JIG-USB-ON");
path = CTRL1_SW_USB;
break;
case MAX14577_MUIC_ADC_FACTORY_MODE_UART_OFF: /* ADC_JIG_UART_OFF */
/* PATH:AP_UART */
strcpy(cable_name, "JIG-UART-OFF");
path = CTRL1_SW_UART;
break;
default:
@ -382,7 +356,7 @@ static int max14577_muic_jig_handler(struct max14577_muic_info *info,
if (ret < 0)
return ret;
extcon_set_cable_state(info->edev, cable_name, attached);
extcon_set_cable_state_(info->edev, EXTCON_JIG, attached);
return 0;
}
@ -479,20 +453,22 @@ static int max14577_muic_chg_handler(struct max14577_muic_info *info)
if (ret < 0)
return ret;
extcon_set_cable_state(info->edev, "USB", attached);
extcon_set_cable_state_(info->edev, EXTCON_USB, attached);
break;
case MAX14577_CHARGER_TYPE_DEDICATED_CHG:
extcon_set_cable_state(info->edev, "TA", attached);
extcon_set_cable_state_(info->edev, EXTCON_TA, attached);
break;
case MAX14577_CHARGER_TYPE_DOWNSTREAM_PORT:
extcon_set_cable_state(info->edev,
"Charge-downstream", attached);
extcon_set_cable_state_(info->edev, EXTCON_CHARGE_DOWNSTREAM,
attached);
break;
case MAX14577_CHARGER_TYPE_SPECIAL_500MA:
extcon_set_cable_state(info->edev, "Slow-charger", attached);
extcon_set_cable_state_(info->edev, EXTCON_SLOW_CHARGER,
attached);
break;
case MAX14577_CHARGER_TYPE_SPECIAL_1A:
extcon_set_cable_state(info->edev, "Fast-charger", attached);
extcon_set_cable_state_(info->edev, EXTCON_FAST_CHARGER,
attached);
break;
case MAX14577_CHARGER_TYPE_NONE:
case MAX14577_CHARGER_TYPE_DEAD_BATTERY:
@ -742,8 +718,6 @@ static int max14577_muic_probe(struct platform_device *pdev)
return -ENOMEM;
}
info->edev->name = dev_name(&pdev->dev);
ret = devm_extcon_dev_register(&pdev->dev, info->edev);
if (ret) {
dev_err(&pdev->dev, "failed to register extcon device\n");

View File

@ -200,44 +200,17 @@ enum max77693_muic_acc_type {
/*
* MAX77693 MUIC device support below list of accessories(external connector)
*/
enum {
EXTCON_CABLE_USB = 0,
EXTCON_CABLE_USB_HOST,
EXTCON_CABLE_TA,
EXTCON_CABLE_FAST_CHARGER,
EXTCON_CABLE_SLOW_CHARGER,
EXTCON_CABLE_CHARGE_DOWNSTREAM,
EXTCON_CABLE_MHL,
EXTCON_CABLE_MHL_TA,
EXTCON_CABLE_JIG_USB_ON,
EXTCON_CABLE_JIG_USB_OFF,
EXTCON_CABLE_JIG_UART_OFF,
EXTCON_CABLE_JIG_UART_ON,
EXTCON_CABLE_DOCK_SMART,
EXTCON_CABLE_DOCK_DESK,
EXTCON_CABLE_DOCK_AUDIO,
_EXTCON_CABLE_NUM,
};
static const char *max77693_extcon_cable[] = {
[EXTCON_CABLE_USB] = "USB",
[EXTCON_CABLE_USB_HOST] = "USB-Host",
[EXTCON_CABLE_TA] = "TA",
[EXTCON_CABLE_FAST_CHARGER] = "Fast-charger",
[EXTCON_CABLE_SLOW_CHARGER] = "Slow-charger",
[EXTCON_CABLE_CHARGE_DOWNSTREAM] = "Charge-downstream",
[EXTCON_CABLE_MHL] = "MHL",
[EXTCON_CABLE_MHL_TA] = "MHL-TA",
[EXTCON_CABLE_JIG_USB_ON] = "JIG-USB-ON",
[EXTCON_CABLE_JIG_USB_OFF] = "JIG-USB-OFF",
[EXTCON_CABLE_JIG_UART_OFF] = "JIG-UART-OFF",
[EXTCON_CABLE_JIG_UART_ON] = "JIG-UART-ON",
[EXTCON_CABLE_DOCK_SMART] = "Dock-Smart",
[EXTCON_CABLE_DOCK_DESK] = "Dock-Desk",
[EXTCON_CABLE_DOCK_AUDIO] = "Dock-Audio",
NULL,
static const unsigned int max77693_extcon_cable[] = {
EXTCON_USB,
EXTCON_USB_HOST,
EXTCON_TA,
EXTCON_FAST_CHARGER,
EXTCON_SLOW_CHARGER,
EXTCON_CHARGE_DOWNSTREAM,
EXTCON_MHL,
EXTCON_JIG,
EXTCON_DOCK,
EXTCON_NONE,
};
/*
@ -484,7 +457,7 @@ static int max77693_muic_dock_handler(struct max77693_muic_info *info,
int ret = 0;
int vbvolt;
bool cable_attached;
char dock_name[CABLE_NAME_MAX];
unsigned int dock_id;
dev_info(info->dev,
"external connector is %s (adc:0x%02x)\n",
@ -507,15 +480,15 @@ static int max77693_muic_dock_handler(struct max77693_muic_info *info,
}
/*
* Notify Dock-Smart/MHL state.
* - Dock-Smart device include three type of cable which
* Notify Dock/MHL state.
* - Dock device include three type of cable which
* are HDMI, USB for mouse/keyboard and micro-usb port
* for USB/TA cable. Dock-Smart device need always exteranl
* power supply(USB/TA cable through micro-usb cable). Dock-
* Smart device support screen output of target to separate
* for USB/TA cable. Dock device need always exteranl
* power supply(USB/TA cable through micro-usb cable). Dock
* device support screen output of target to separate
* monitor and mouse/keyboard for desktop mode.
*
* Features of 'USB/TA cable with Dock-Smart device'
* Features of 'USB/TA cable with Dock device'
* - Support MHL
* - Support external output feature of audio
* - Support charging through micro-usb port without data
@ -529,16 +502,16 @@ static int max77693_muic_dock_handler(struct max77693_muic_info *info,
if (ret < 0)
return ret;
extcon_set_cable_state(info->edev, "Dock-Smart", attached);
extcon_set_cable_state(info->edev, "MHL", attached);
extcon_set_cable_state_(info->edev, EXTCON_DOCK, attached);
extcon_set_cable_state_(info->edev, EXTCON_MHL, attached);
goto out;
case MAX77693_MUIC_ADC_AUDIO_MODE_REMOTE: /* Dock-Desk */
strcpy(dock_name, "Dock-Desk");
dock_id = EXTCON_DOCK;
break;
case MAX77693_MUIC_ADC_AV_CABLE_NOLOAD: /* Dock-Audio */
strcpy(dock_name, "Dock-Audio");
dock_id = EXTCON_DOCK;
if (!attached)
extcon_set_cable_state(info->edev, "USB", false);
extcon_set_cable_state_(info->edev, EXTCON_USB, false);
break;
default:
dev_err(info->dev, "failed to detect %s dock device\n",
@ -550,7 +523,7 @@ static int max77693_muic_dock_handler(struct max77693_muic_info *info,
ret = max77693_muic_set_path(info, CONTROL1_SW_AUDIO, attached);
if (ret < 0)
return ret;
extcon_set_cable_state(info->edev, dock_name, attached);
extcon_set_cable_state_(info->edev, dock_id, attached);
out:
return 0;
@ -615,20 +588,19 @@ static int max77693_muic_adc_ground_handler(struct max77693_muic_info *info)
ret = max77693_muic_set_path(info, CONTROL1_SW_USB, attached);
if (ret < 0)
return ret;
extcon_set_cable_state(info->edev, "USB-Host", attached);
extcon_set_cable_state_(info->edev, EXTCON_USB_HOST, attached);
break;
case MAX77693_MUIC_GND_AV_CABLE_LOAD:
/* Audio Video Cable with load, PATH:AUDIO */
ret = max77693_muic_set_path(info, CONTROL1_SW_AUDIO, attached);
if (ret < 0)
return ret;
extcon_set_cable_state(info->edev,
"Audio-video-load", attached);
extcon_set_cable_state_(info->edev, EXTCON_USB, attached);
break;
case MAX77693_MUIC_GND_MHL:
case MAX77693_MUIC_GND_MHL_VB:
/* MHL or MHL with USB/TA cable */
extcon_set_cable_state(info->edev, "MHL", attached);
extcon_set_cable_state_(info->edev, EXTCON_MHL, attached);
break;
default:
dev_err(info->dev, "failed to detect %s cable of gnd type\n",
@ -642,7 +614,6 @@ static int max77693_muic_adc_ground_handler(struct max77693_muic_info *info)
static int max77693_muic_jig_handler(struct max77693_muic_info *info,
int cable_type, bool attached)
{
char cable_name[32];
int ret = 0;
u8 path = CONTROL1_SW_OPEN;
@ -652,23 +623,13 @@ static int max77693_muic_jig_handler(struct max77693_muic_info *info,
switch (cable_type) {
case MAX77693_MUIC_ADC_FACTORY_MODE_USB_OFF: /* ADC_JIG_USB_OFF */
/* PATH:AP_USB */
strcpy(cable_name, "JIG-USB-OFF");
path = CONTROL1_SW_USB;
break;
case MAX77693_MUIC_ADC_FACTORY_MODE_USB_ON: /* ADC_JIG_USB_ON */
/* PATH:AP_USB */
strcpy(cable_name, "JIG-USB-ON");
path = CONTROL1_SW_USB;
break;
case MAX77693_MUIC_ADC_FACTORY_MODE_UART_OFF: /* ADC_JIG_UART_OFF */
/* PATH:AP_UART */
strcpy(cable_name, "JIG-UART-OFF");
path = CONTROL1_SW_UART;
break;
case MAX77693_MUIC_ADC_FACTORY_MODE_UART_ON: /* ADC_JIG_UART_ON */
/* PATH:AP_UART */
strcpy(cable_name, "JIG-UART-ON");
path = CONTROL1_SW_UART;
break;
default:
@ -681,7 +642,7 @@ static int max77693_muic_jig_handler(struct max77693_muic_info *info,
if (ret < 0)
return ret;
extcon_set_cable_state(info->edev, cable_name, attached);
extcon_set_cable_state_(info->edev, EXTCON_JIG, attached);
return 0;
}
@ -823,22 +784,22 @@ static int max77693_muic_chg_handler(struct max77693_muic_info *info)
case MAX77693_MUIC_GND_MHL:
case MAX77693_MUIC_GND_MHL_VB:
/*
* MHL cable with MHL-TA(USB/TA) cable
* MHL cable with USB/TA cable
* - MHL cable include two port(HDMI line and separate
* micro-usb port. When the target connect MHL cable,
* extcon driver check whether MHL-TA(USB/TA) cable is
* connected. If MHL-TA cable is connected, extcon
* extcon driver check whether USB/TA cable is
* connected. If USB/TA cable is connected, extcon
* driver notify state to notifiee for charging battery.
*
* Features of 'MHL-TA(USB/TA) with MHL cable'
* Features of 'USB/TA with MHL cable'
* - Support MHL
* - Support charging through micro-usb port without
* data connection
*/
extcon_set_cable_state(info->edev, "MHL-TA", attached);
extcon_set_cable_state_(info->edev, EXTCON_TA, attached);
if (!cable_attached)
extcon_set_cable_state(info->edev,
"MHL", cable_attached);
extcon_set_cable_state_(info->edev, EXTCON_MHL,
cable_attached);
break;
}
@ -861,11 +822,12 @@ static int max77693_muic_chg_handler(struct max77693_muic_info *info)
* - Support charging through micro-usb port without
* data connection.
*/
extcon_set_cable_state(info->edev, "USB", attached);
extcon_set_cable_state_(info->edev, EXTCON_USB,
attached);
if (!cable_attached)
extcon_set_cable_state(info->edev, "Dock-Audio",
cable_attached);
extcon_set_cable_state_(info->edev, EXTCON_DOCK,
cable_attached);
break;
case MAX77693_MUIC_ADC_RESERVED_ACC_3: /* Dock-Smart */
/*
@ -893,10 +855,10 @@ static int max77693_muic_chg_handler(struct max77693_muic_info *info)
if (ret < 0)
return ret;
extcon_set_cable_state(info->edev, "Dock-Smart",
attached);
extcon_set_cable_state(info->edev, "MHL", attached);
extcon_set_cable_state_(info->edev, EXTCON_DOCK,
attached);
extcon_set_cable_state_(info->edev, EXTCON_MHL,
attached);
break;
}
@ -929,23 +891,26 @@ static int max77693_muic_chg_handler(struct max77693_muic_info *info)
if (ret < 0)
return ret;
extcon_set_cable_state(info->edev, "USB", attached);
extcon_set_cable_state_(info->edev, EXTCON_USB,
attached);
break;
case MAX77693_CHARGER_TYPE_DEDICATED_CHG:
/* Only TA cable */
extcon_set_cable_state(info->edev, "TA", attached);
extcon_set_cable_state_(info->edev, EXTCON_TA, attached);
break;
}
break;
case MAX77693_CHARGER_TYPE_DOWNSTREAM_PORT:
extcon_set_cable_state(info->edev,
"Charge-downstream", attached);
extcon_set_cable_state_(info->edev, EXTCON_CHARGE_DOWNSTREAM,
attached);
break;
case MAX77693_CHARGER_TYPE_APPLE_500MA:
extcon_set_cable_state(info->edev, "Slow-charger", attached);
extcon_set_cable_state_(info->edev, EXTCON_SLOW_CHARGER,
attached);
break;
case MAX77693_CHARGER_TYPE_APPLE_1A_2A:
extcon_set_cable_state(info->edev, "Fast-charger", attached);
extcon_set_cable_state_(info->edev, EXTCON_FAST_CHARGER,
attached);
break;
case MAX77693_CHARGER_TYPE_DEAD_BATTERY:
break;
@ -1182,7 +1147,6 @@ static int max77693_muic_probe(struct platform_device *pdev)
dev_err(&pdev->dev, "failed to allocate memory for extcon\n");
return -ENOMEM;
}
info->edev->name = DEV_NAME;
ret = devm_extcon_dev_register(&pdev->dev, info->edev);
if (ret) {

View File

@ -118,36 +118,16 @@ enum max77843_muic_charger_type {
MAX77843_MUIC_CHG_GND,
};
enum {
MAX77843_CABLE_USB = 0,
MAX77843_CABLE_USB_HOST,
MAX77843_CABLE_TA,
MAX77843_CABLE_CHARGE_DOWNSTREAM,
MAX77843_CABLE_FAST_CHARGER,
MAX77843_CABLE_SLOW_CHARGER,
MAX77843_CABLE_MHL,
MAX77843_CABLE_MHL_TA,
MAX77843_CABLE_JIG_USB_ON,
MAX77843_CABLE_JIG_USB_OFF,
MAX77843_CABLE_JIG_UART_ON,
MAX77843_CABLE_JIG_UART_OFF,
MAX77843_CABLE_NUM,
};
static const char *max77843_extcon_cable[] = {
[MAX77843_CABLE_USB] = "USB",
[MAX77843_CABLE_USB_HOST] = "USB-HOST",
[MAX77843_CABLE_TA] = "TA",
[MAX77843_CABLE_CHARGE_DOWNSTREAM] = "CHARGER-DOWNSTREAM",
[MAX77843_CABLE_FAST_CHARGER] = "FAST-CHARGER",
[MAX77843_CABLE_SLOW_CHARGER] = "SLOW-CHARGER",
[MAX77843_CABLE_MHL] = "MHL",
[MAX77843_CABLE_MHL_TA] = "MHL-TA",
[MAX77843_CABLE_JIG_USB_ON] = "JIG-USB-ON",
[MAX77843_CABLE_JIG_USB_OFF] = "JIG-USB-OFF",
[MAX77843_CABLE_JIG_UART_ON] = "JIG-UART-ON",
[MAX77843_CABLE_JIG_UART_OFF] = "JIG-UART-OFF",
static const unsigned int max77843_extcon_cable[] = {
EXTCON_USB,
EXTCON_USB_HOST,
EXTCON_TA,
EXTCON_CHARGE_DOWNSTREAM,
EXTCON_FAST_CHARGER,
EXTCON_SLOW_CHARGER,
EXTCON_MHL,
EXTCON_JIG,
EXTCON_NONE,
};
struct max77843_muic_irq {
@ -362,7 +342,7 @@ static int max77843_muic_adc_gnd_handler(struct max77843_muic_info *info)
if (ret < 0)
return ret;
extcon_set_cable_state(info->edev, "USB-HOST", attached);
extcon_set_cable_state_(info->edev, EXTCON_USB_HOST, attached);
break;
case MAX77843_MUIC_GND_MHL_VB:
case MAX77843_MUIC_GND_MHL:
@ -370,7 +350,7 @@ static int max77843_muic_adc_gnd_handler(struct max77843_muic_info *info)
if (ret < 0)
return ret;
extcon_set_cable_state(info->edev, "MHL", attached);
extcon_set_cable_state_(info->edev, EXTCON_MHL, attached);
break;
default:
dev_err(info->dev, "failed to detect %s accessory(gnd:0x%x)\n",
@ -385,36 +365,29 @@ static int max77843_muic_jig_handler(struct max77843_muic_info *info,
int cable_type, bool attached)
{
int ret;
u8 path = CONTROL1_SW_OPEN;
dev_dbg(info->dev, "external connector is %s (adc:0x%02x)\n",
attached ? "attached" : "detached", cable_type);
switch (cable_type) {
case MAX77843_MUIC_ADC_FACTORY_MODE_USB_OFF:
ret = max77843_muic_set_path(info, CONTROL1_SW_USB, attached);
if (ret < 0)
return ret;
extcon_set_cable_state(info->edev, "JIG-USB-OFF", attached);
break;
case MAX77843_MUIC_ADC_FACTORY_MODE_USB_ON:
ret = max77843_muic_set_path(info, CONTROL1_SW_USB, attached);
if (ret < 0)
return ret;
extcon_set_cable_state(info->edev, "JIG-USB-ON", attached);
path = CONTROL1_SW_USB;
break;
case MAX77843_MUIC_ADC_FACTORY_MODE_UART_OFF:
ret = max77843_muic_set_path(info, CONTROL1_SW_UART, attached);
if (ret < 0)
return ret;
extcon_set_cable_state(info->edev, "JIG-UART-OFF", attached);
path = CONTROL1_SW_UART;
break;
default:
ret = max77843_muic_set_path(info, CONTROL1_SW_OPEN, attached);
if (ret < 0)
return ret;
break;
return -EINVAL;
}
ret = max77843_muic_set_path(info, path, attached);
if (ret < 0)
return ret;
extcon_set_cable_state_(info->edev, EXTCON_JIG, attached);
return 0;
}
@ -505,36 +478,38 @@ static int max77843_muic_chg_handler(struct max77843_muic_info *info)
if (ret < 0)
return ret;
extcon_set_cable_state(info->edev, "USB", attached);
extcon_set_cable_state_(info->edev, EXTCON_USB, attached);
break;
case MAX77843_MUIC_CHG_DOWNSTREAM:
ret = max77843_muic_set_path(info, CONTROL1_SW_OPEN, attached);
if (ret < 0)
return ret;
extcon_set_cable_state(info->edev,
"CHARGER-DOWNSTREAM", attached);
extcon_set_cable_state_(info->edev, EXTCON_CHARGE_DOWNSTREAM,
attached);
break;
case MAX77843_MUIC_CHG_DEDICATED:
ret = max77843_muic_set_path(info, CONTROL1_SW_OPEN, attached);
if (ret < 0)
return ret;
extcon_set_cable_state(info->edev, "TA", attached);
extcon_set_cable_state_(info->edev, EXTCON_TA, attached);
break;
case MAX77843_MUIC_CHG_SPECIAL_500MA:
ret = max77843_muic_set_path(info, CONTROL1_SW_OPEN, attached);
if (ret < 0)
return ret;
extcon_set_cable_state(info->edev, "SLOW-CHAREGER", attached);
extcon_set_cable_state_(info->edev, EXTCON_SLOW_CHARGER,
attached);
break;
case MAX77843_MUIC_CHG_SPECIAL_1A:
ret = max77843_muic_set_path(info, CONTROL1_SW_OPEN, attached);
if (ret < 0)
return ret;
extcon_set_cable_state(info->edev, "FAST-CHARGER", attached);
extcon_set_cable_state_(info->edev, EXTCON_FAST_CHARGER,
attached);
break;
case MAX77843_MUIC_CHG_GND:
gnd_type = max77843_muic_get_cable_type(info,
@ -542,9 +517,9 @@ static int max77843_muic_chg_handler(struct max77843_muic_info *info)
/* Charger cable on MHL accessory is attach or detach */
if (gnd_type == MAX77843_MUIC_GND_MHL_VB)
extcon_set_cable_state(info->edev, "MHL-TA", true);
extcon_set_cable_state_(info->edev, EXTCON_TA, true);
else if (gnd_type == MAX77843_MUIC_GND_MHL)
extcon_set_cable_state(info->edev, "MHL-TA", false);
extcon_set_cable_state_(info->edev, EXTCON_TA, false);
break;
case MAX77843_MUIC_CHG_NONE:
break;

View File

@ -145,34 +145,17 @@ struct max8997_muic_info {
int path_uart;
};
enum {
EXTCON_CABLE_USB = 0,
EXTCON_CABLE_USB_HOST,
EXTCON_CABLE_TA,
EXTCON_CABLE_FAST_CHARGER,
EXTCON_CABLE_SLOW_CHARGER,
EXTCON_CABLE_CHARGE_DOWNSTREAM,
EXTCON_CABLE_MHL,
EXTCON_CABLE_DOCK_DESK,
EXTCON_CABLE_DOCK_CARD,
EXTCON_CABLE_JIG,
_EXTCON_CABLE_NUM,
};
static const char *max8997_extcon_cable[] = {
[EXTCON_CABLE_USB] = "USB",
[EXTCON_CABLE_USB_HOST] = "USB-Host",
[EXTCON_CABLE_TA] = "TA",
[EXTCON_CABLE_FAST_CHARGER] = "Fast-charger",
[EXTCON_CABLE_SLOW_CHARGER] = "Slow-charger",
[EXTCON_CABLE_CHARGE_DOWNSTREAM] = "Charge-downstream",
[EXTCON_CABLE_MHL] = "MHL",
[EXTCON_CABLE_DOCK_DESK] = "Dock-Desk",
[EXTCON_CABLE_DOCK_CARD] = "Dock-Card",
[EXTCON_CABLE_JIG] = "JIG",
NULL,
static const unsigned int max8997_extcon_cable[] = {
EXTCON_USB,
EXTCON_USB_HOST,
EXTCON_TA,
EXTCON_FAST_CHARGER,
EXTCON_SLOW_CHARGER,
EXTCON_CHARGE_DOWNSTREAM,
EXTCON_MHL,
EXTCON_DOCK,
EXTCON_JIG,
EXTCON_NONE,
};
/*
@ -347,10 +330,10 @@ static int max8997_muic_handle_usb(struct max8997_muic_info *info,
switch (usb_type) {
case MAX8997_USB_HOST:
extcon_set_cable_state(info->edev, "USB-Host", attached);
extcon_set_cable_state_(info->edev, EXTCON_USB_HOST, attached);
break;
case MAX8997_USB_DEVICE:
extcon_set_cable_state(info->edev, "USB", attached);
extcon_set_cable_state_(info->edev, EXTCON_USB, attached);
break;
default:
dev_err(info->dev, "failed to detect %s usb cable\n",
@ -374,10 +357,8 @@ static int max8997_muic_handle_dock(struct max8997_muic_info *info,
switch (cable_type) {
case MAX8997_MUIC_ADC_AV_CABLE_NOLOAD:
extcon_set_cable_state(info->edev, "Dock-desk", attached);
break;
case MAX8997_MUIC_ADC_FACTORY_MODE_UART_ON:
extcon_set_cable_state(info->edev, "Dock-card", attached);
extcon_set_cable_state_(info->edev, EXTCON_DOCK, attached);
break;
default:
dev_err(info->dev, "failed to detect %s dock device\n",
@ -400,7 +381,7 @@ static int max8997_muic_handle_jig_uart(struct max8997_muic_info *info,
return ret;
}
extcon_set_cable_state(info->edev, "JIG", attached);
extcon_set_cable_state_(info->edev, EXTCON_JIG, attached);
return 0;
}
@ -422,7 +403,7 @@ static int max8997_muic_adc_handler(struct max8997_muic_info *info)
return ret;
break;
case MAX8997_MUIC_ADC_MHL:
extcon_set_cable_state(info->edev, "MHL", attached);
extcon_set_cable_state_(info->edev, EXTCON_MHL, attached);
break;
case MAX8997_MUIC_ADC_FACTORY_MODE_USB_OFF:
case MAX8997_MUIC_ADC_FACTORY_MODE_USB_ON:
@ -505,17 +486,19 @@ static int max8997_muic_chg_handler(struct max8997_muic_info *info)
}
break;
case MAX8997_CHARGER_TYPE_DOWNSTREAM_PORT:
extcon_set_cable_state(info->edev,
"Charge-downstream", attached);
extcon_set_cable_state_(info->edev, EXTCON_CHARGE_DOWNSTREAM,
attached);
break;
case MAX8997_CHARGER_TYPE_DEDICATED_CHG:
extcon_set_cable_state(info->edev, "TA", attached);
extcon_set_cable_state_(info->edev, EXTCON_TA, attached);
break;
case MAX8997_CHARGER_TYPE_500MA:
extcon_set_cable_state(info->edev, "Slow-charger", attached);
extcon_set_cable_state_(info->edev, EXTCON_SLOW_CHARGER,
attached);
break;
case MAX8997_CHARGER_TYPE_1A:
extcon_set_cable_state(info->edev, "Fast-charger", attached);
extcon_set_cable_state_(info->edev, EXTCON_FAST_CHARGER,
attached);
break;
default:
dev_err(info->dev,
@ -700,7 +683,6 @@ static int max8997_muic_probe(struct platform_device *pdev)
ret = -ENOMEM;
goto err_irq;
}
info->edev->name = DEV_NAME;
ret = devm_extcon_dev_register(&pdev->dev, info->edev);
if (ret) {

View File

@ -29,10 +29,10 @@
#include <linux/of.h>
#include <linux/of_platform.h>
static const char *palmas_extcon_cable[] = {
[0] = "USB",
[1] = "USB-HOST",
NULL,
static const unsigned int palmas_extcon_cable[] = {
EXTCON_USB,
EXTCON_USB_HOST,
EXTCON_NONE,
};
static const int mutually_exclusive[] = {0x3, 0x0};
@ -49,6 +49,7 @@ static void palmas_usb_wakeup(struct palmas *palmas, int enable)
static irqreturn_t palmas_vbus_irq_handler(int irq, void *_palmas_usb)
{
struct palmas_usb *palmas_usb = _palmas_usb;
struct extcon_dev *edev = palmas_usb->edev;
unsigned int vbus_line_state;
palmas_read(palmas_usb->palmas, PALMAS_INTERRUPT_BASE,
@ -57,7 +58,7 @@ static irqreturn_t palmas_vbus_irq_handler(int irq, void *_palmas_usb)
if (vbus_line_state & PALMAS_INT3_LINE_STATE_VBUS) {
if (palmas_usb->linkstat != PALMAS_USB_STATE_VBUS) {
palmas_usb->linkstat = PALMAS_USB_STATE_VBUS;
extcon_set_cable_state(palmas_usb->edev, "USB", true);
extcon_set_cable_state_(edev, EXTCON_USB, true);
dev_info(palmas_usb->dev, "USB cable is attached\n");
} else {
dev_dbg(palmas_usb->dev,
@ -66,7 +67,7 @@ static irqreturn_t palmas_vbus_irq_handler(int irq, void *_palmas_usb)
} else if (!(vbus_line_state & PALMAS_INT3_LINE_STATE_VBUS)) {
if (palmas_usb->linkstat == PALMAS_USB_STATE_VBUS) {
palmas_usb->linkstat = PALMAS_USB_STATE_DISCONNECT;
extcon_set_cable_state(palmas_usb->edev, "USB", false);
extcon_set_cable_state_(edev, EXTCON_USB, false);
dev_info(palmas_usb->dev, "USB cable is detached\n");
} else {
dev_dbg(palmas_usb->dev,
@ -81,6 +82,7 @@ static irqreturn_t palmas_id_irq_handler(int irq, void *_palmas_usb)
{
unsigned int set, id_src;
struct palmas_usb *palmas_usb = _palmas_usb;
struct extcon_dev *edev = palmas_usb->edev;
palmas_read(palmas_usb->palmas, PALMAS_USB_OTG_BASE,
PALMAS_USB_ID_INT_LATCH_SET, &set);
@ -93,7 +95,7 @@ static irqreturn_t palmas_id_irq_handler(int irq, void *_palmas_usb)
PALMAS_USB_ID_INT_LATCH_CLR,
PALMAS_USB_ID_INT_EN_HI_CLR_ID_GND);
palmas_usb->linkstat = PALMAS_USB_STATE_ID;
extcon_set_cable_state(palmas_usb->edev, "USB-HOST", true);
extcon_set_cable_state_(edev, EXTCON_USB_HOST, true);
dev_info(palmas_usb->dev, "USB-HOST cable is attached\n");
} else if ((set & PALMAS_USB_ID_INT_SRC_ID_FLOAT) &&
(id_src & PALMAS_USB_ID_INT_SRC_ID_FLOAT)) {
@ -101,17 +103,17 @@ static irqreturn_t palmas_id_irq_handler(int irq, void *_palmas_usb)
PALMAS_USB_ID_INT_LATCH_CLR,
PALMAS_USB_ID_INT_EN_HI_CLR_ID_FLOAT);
palmas_usb->linkstat = PALMAS_USB_STATE_DISCONNECT;
extcon_set_cable_state(palmas_usb->edev, "USB-HOST", false);
extcon_set_cable_state_(edev, EXTCON_USB_HOST, false);
dev_info(palmas_usb->dev, "USB-HOST cable is detached\n");
} else if ((palmas_usb->linkstat == PALMAS_USB_STATE_ID) &&
(!(set & PALMAS_USB_ID_INT_SRC_ID_GND))) {
palmas_usb->linkstat = PALMAS_USB_STATE_DISCONNECT;
extcon_set_cable_state(palmas_usb->edev, "USB-HOST", false);
extcon_set_cable_state_(edev, EXTCON_USB_HOST, false);
dev_info(palmas_usb->dev, "USB-HOST cable is detached\n");
} else if ((palmas_usb->linkstat == PALMAS_USB_STATE_DISCONNECT) &&
(id_src & PALMAS_USB_ID_INT_SRC_ID_GND)) {
palmas_usb->linkstat = PALMAS_USB_STATE_ID;
extcon_set_cable_state(palmas_usb->edev, "USB-HOST", true);
extcon_set_cable_state_(edev, EXTCON_USB_HOST, true);
dev_info(palmas_usb->dev, " USB-HOST cable is attached\n");
}
@ -193,7 +195,6 @@ static int palmas_usb_probe(struct platform_device *pdev)
dev_err(&pdev->dev, "failed to allocate extcon device\n");
return -ENOMEM;
}
palmas_usb->edev->name = kstrdup(node->name, GFP_KERNEL);
palmas_usb->edev->mutually_exclusive = mutually_exclusive;
status = devm_extcon_dev_register(&pdev->dev, palmas_usb->edev);

View File

@ -90,27 +90,12 @@ static struct reg_data rt8973a_reg_data[] = {
};
/* List of detectable cables */
enum {
EXTCON_CABLE_USB = 0,
EXTCON_CABLE_USB_HOST,
EXTCON_CABLE_TA,
EXTCON_CABLE_JIG_OFF_USB,
EXTCON_CABLE_JIG_ON_USB,
EXTCON_CABLE_JIG_OFF_UART,
EXTCON_CABLE_JIG_ON_UART,
EXTCON_CABLE_END,
};
static const char *rt8973a_extcon_cable[] = {
[EXTCON_CABLE_USB] = "USB",
[EXTCON_CABLE_USB_HOST] = "USB-Host",
[EXTCON_CABLE_TA] = "TA",
[EXTCON_CABLE_JIG_OFF_USB] = "JIG-USB-OFF",
[EXTCON_CABLE_JIG_ON_USB] = "JIG-USB-ON",
[EXTCON_CABLE_JIG_OFF_UART] = "JIG-UART-OFF",
[EXTCON_CABLE_JIG_ON_UART] = "JIG-UART-ON",
NULL,
static const unsigned int rt8973a_extcon_cable[] = {
EXTCON_USB,
EXTCON_USB_HOST,
EXTCON_TA,
EXTCON_JIG,
EXTCON_NONE,
};
/* Define OVP (Over Voltage Protection), OTP (Over Temperature Protection) */
@ -313,14 +298,11 @@ static int rt8973a_muic_cable_handler(struct rt8973a_muic_info *info,
enum rt8973a_event_type event)
{
static unsigned int prev_cable_type;
const char **cable_names = info->edev->supported_cable;
unsigned int con_sw = DM_DP_SWITCH_UART;
int ret, idx = 0, cable_type;
int ret, cable_type;
unsigned int id;
bool attached = false;
if (!cable_names)
return 0;
switch (event) {
case RT8973A_EVENT_ATTACH:
cable_type = rt8973a_muic_get_cable_type(info);
@ -347,31 +329,25 @@ static int rt8973a_muic_cable_handler(struct rt8973a_muic_info *info,
switch (cable_type) {
case RT8973A_MUIC_ADC_OTG:
idx = EXTCON_CABLE_USB_HOST;
id = EXTCON_USB_HOST;
con_sw = DM_DP_SWITCH_USB;
break;
case RT8973A_MUIC_ADC_TA:
idx = EXTCON_CABLE_TA;
id = EXTCON_TA;
con_sw = DM_DP_SWITCH_OPEN;
break;
case RT8973A_MUIC_ADC_FACTORY_MODE_BOOT_OFF_USB:
idx = EXTCON_CABLE_JIG_OFF_USB;
con_sw = DM_DP_SWITCH_UART;
break;
case RT8973A_MUIC_ADC_FACTORY_MODE_BOOT_ON_USB:
idx = EXTCON_CABLE_JIG_ON_USB;
con_sw = DM_DP_SWITCH_UART;
id = EXTCON_JIG;
con_sw = DM_DP_SWITCH_USB;
break;
case RT8973A_MUIC_ADC_FACTORY_MODE_BOOT_OFF_UART:
idx = EXTCON_CABLE_JIG_OFF_UART;
con_sw = DM_DP_SWITCH_UART;
break;
case RT8973A_MUIC_ADC_FACTORY_MODE_BOOT_ON_UART:
idx = EXTCON_CABLE_JIG_ON_UART;
id = EXTCON_JIG;
con_sw = DM_DP_SWITCH_UART;
break;
case RT8973A_MUIC_ADC_USB:
idx = EXTCON_CABLE_USB;
id = EXTCON_USB;
con_sw = DM_DP_SWITCH_USB;
break;
case RT8973A_MUIC_ADC_OPEN:
@ -421,7 +397,7 @@ static int rt8973a_muic_cable_handler(struct rt8973a_muic_info *info,
return ret;
/* Change the state of external accessory */
extcon_set_cable_state(info->edev, cable_names[idx], attached);
extcon_set_cable_state_(info->edev, id, attached);
return 0;
}
@ -643,7 +619,6 @@ static int rt8973a_muic_i2c_probe(struct i2c_client *i2c,
dev_err(info->dev, "failed to allocate memory for extcon\n");
return -ENOMEM;
}
info->edev->name = np->name;
/* Register extcon device */
ret = devm_extcon_dev_register(info->dev, info->edev);

View File

@ -92,19 +92,11 @@ static struct reg_data sm5502_reg_data[] = {
};
/* List of detectable cables */
enum {
EXTCON_CABLE_USB = 0,
EXTCON_CABLE_USB_HOST,
EXTCON_CABLE_TA,
EXTCON_CABLE_END,
};
static const char *sm5502_extcon_cable[] = {
[EXTCON_CABLE_USB] = "USB",
[EXTCON_CABLE_USB_HOST] = "USB-Host",
[EXTCON_CABLE_TA] = "TA",
NULL,
static const unsigned int sm5502_extcon_cable[] = {
EXTCON_USB,
EXTCON_USB_HOST,
EXTCON_TA,
EXTCON_NONE,
};
/* Define supported accessory type */
@ -377,16 +369,12 @@ static int sm5502_muic_cable_handler(struct sm5502_muic_info *info,
bool attached)
{
static unsigned int prev_cable_type = SM5502_MUIC_ADC_GROUND;
const char **cable_names = info->edev->supported_cable;
unsigned int cable_type = SM5502_MUIC_ADC_GROUND;
unsigned int con_sw = DM_DP_SWITCH_OPEN;
unsigned int vbus_sw = VBUSIN_SWITCH_OPEN;
unsigned int idx = 0;
unsigned int id;
int ret;
if (!cable_names)
return 0;
/* Get the type of attached or detached cable */
if (attached)
cable_type = sm5502_muic_get_cable_type(info);
@ -396,17 +384,17 @@ static int sm5502_muic_cable_handler(struct sm5502_muic_info *info,
switch (cable_type) {
case SM5502_MUIC_ADC_OPEN_USB:
idx = EXTCON_CABLE_USB;
id = EXTCON_USB;
con_sw = DM_DP_SWITCH_USB;
vbus_sw = VBUSIN_SWITCH_VBUSOUT_WITH_USB;
break;
case SM5502_MUIC_ADC_OPEN_TA:
idx = EXTCON_CABLE_TA;
id = EXTCON_TA;
con_sw = DM_DP_SWITCH_OPEN;
vbus_sw = VBUSIN_SWITCH_VBUSOUT;
break;
case SM5502_MUIC_ADC_OPEN_USB_OTG:
idx = EXTCON_CABLE_USB_HOST;
id = EXTCON_USB_HOST;
con_sw = DM_DP_SWITCH_USB;
vbus_sw = VBUSIN_SWITCH_OPEN;
break;
@ -422,7 +410,7 @@ static int sm5502_muic_cable_handler(struct sm5502_muic_info *info,
return ret;
/* Change the state of external accessory */
extcon_set_cable_state(info->edev, cable_names[idx], attached);
extcon_set_cable_state_(info->edev, id, attached);
return 0;
}
@ -623,7 +611,6 @@ static int sm5022_muic_i2c_probe(struct i2c_client *i2c,
dev_err(info->dev, "failed to allocate memory for extcon\n");
return -ENOMEM;
}
info->edev->name = np->name;
/* Register extcon device */
ret = devm_extcon_dev_register(info->dev, info->edev);

View File

@ -15,6 +15,7 @@
*/
#include <linux/extcon.h>
#include <linux/gpio/consumer.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
@ -38,18 +39,10 @@ struct usb_extcon_info {
struct delayed_work wq_detcable;
};
/* List of detectable cables */
enum {
EXTCON_CABLE_USB = 0,
EXTCON_CABLE_USB_HOST,
EXTCON_CABLE_END,
};
static const char *usb_extcon_cable[] = {
[EXTCON_CABLE_USB] = "USB",
[EXTCON_CABLE_USB_HOST] = "USB-HOST",
NULL,
static const unsigned int usb_extcon_cable[] = {
EXTCON_USB,
EXTCON_USB_HOST,
EXTCON_NONE,
};
static void usb_extcon_detect_cable(struct work_struct *work)
@ -67,24 +60,16 @@ static void usb_extcon_detect_cable(struct work_struct *work)
* As we don't have event for USB peripheral cable attached,
* we simulate USB peripheral attach here.
*/
extcon_set_cable_state(info->edev,
usb_extcon_cable[EXTCON_CABLE_USB_HOST],
false);
extcon_set_cable_state(info->edev,
usb_extcon_cable[EXTCON_CABLE_USB],
true);
extcon_set_cable_state_(info->edev, EXTCON_USB_HOST, false);
extcon_set_cable_state_(info->edev, EXTCON_USB, true);
} else {
/*
* ID = 0 means USB HOST cable attached.
* As we don't have event for USB peripheral cable detached,
* we simulate USB peripheral detach here.
*/
extcon_set_cable_state(info->edev,
usb_extcon_cable[EXTCON_CABLE_USB],
false);
extcon_set_cable_state(info->edev,
usb_extcon_cable[EXTCON_CABLE_USB_HOST],
true);
extcon_set_cable_state_(info->edev, EXTCON_USB, false);
extcon_set_cable_state_(info->edev, EXTCON_USB_HOST, true);
}
}
@ -113,7 +98,7 @@ static int usb_extcon_probe(struct platform_device *pdev)
return -ENOMEM;
info->dev = dev;
info->id_gpiod = devm_gpiod_get(&pdev->dev, "id");
info->id_gpiod = devm_gpiod_get(&pdev->dev, "id", GPIOD_IN);
if (IS_ERR(info->id_gpiod)) {
dev_err(dev, "failed to get ID GPIO\n");
return PTR_ERR(info->id_gpiod);

View File

@ -1,8 +1,11 @@
/*
* drivers/extcon/extcon_class.c
* drivers/extcon/extcon.c - External Connector (extcon) framework.
*
* External connector (extcon) class driver
*
* Copyright (C) 2015 Samsung Electronics
* Author: Chanwoo Choi <cw00.choi@samsung.com>
*
* Copyright (C) 2012 Samsung Electronics
* Author: Donggeun Kim <dg77.kim@samsung.com>
* Author: MyungJoo Ham <myungjoo.ham@samsung.com>
@ -19,8 +22,7 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
*/
#include <linux/module.h>
#include <linux/types.h>
@ -33,36 +35,43 @@
#include <linux/slab.h>
#include <linux/sysfs.h>
/*
* extcon_cable_name suggests the standard cable names for commonly used
* cable types.
*
* However, please do not use extcon_cable_name directly for extcon_dev
* struct's supported_cable pointer unless your device really supports
* every single port-type of the following cable names. Please choose cable
* names that are actually used in your extcon device.
*/
const char extcon_cable_name[][CABLE_NAME_MAX + 1] = {
#define SUPPORTED_CABLE_MAX 32
#define CABLE_NAME_MAX 30
static const char *extcon_name[] = {
[EXTCON_NONE] = "NONE",
/* USB external connector */
[EXTCON_USB] = "USB",
[EXTCON_USB_HOST] = "USB-Host",
[EXTCON_USB_HOST] = "USB-HOST",
/* Charger external connector */
[EXTCON_TA] = "TA",
[EXTCON_FAST_CHARGER] = "Fast-charger",
[EXTCON_SLOW_CHARGER] = "Slow-charger",
[EXTCON_CHARGE_DOWNSTREAM] = "Charge-downstream",
[EXTCON_FAST_CHARGER] = "FAST-CHARGER",
[EXTCON_SLOW_CHARGER] = "SLOW-CHARGER",
[EXTCON_CHARGE_DOWNSTREAM] = "CHARGE-DOWNSTREAM",
/* Audio/Video external connector */
[EXTCON_LINE_IN] = "LINE-IN",
[EXTCON_LINE_OUT] = "LINE-OUT",
[EXTCON_MICROPHONE] = "MICROPHONE",
[EXTCON_HEADPHONE] = "HEADPHONE",
[EXTCON_HDMI] = "HDMI",
[EXTCON_MHL] = "MHL",
[EXTCON_DVI] = "DVI",
[EXTCON_VGA] = "VGA",
[EXTCON_DOCK] = "Dock",
[EXTCON_LINE_IN] = "Line-in",
[EXTCON_LINE_OUT] = "Line-out",
[EXTCON_MIC_IN] = "Microphone",
[EXTCON_HEADPHONE_OUT] = "Headphone",
[EXTCON_SPDIF_IN] = "SPDIF-in",
[EXTCON_SPDIF_OUT] = "SPDIF-out",
[EXTCON_VIDEO_IN] = "Video-in",
[EXTCON_VIDEO_OUT] = "Video-out",
[EXTCON_MECHANICAL] = "Mechanical",
[EXTCON_SPDIF_IN] = "SPDIF-IN",
[EXTCON_SPDIF_OUT] = "SPDIF-OUT",
[EXTCON_VIDEO_IN] = "VIDEO-IN",
[EXTCON_VIDEO_OUT] = "VIDEO-OUT",
/* Etc external connector */
[EXTCON_DOCK] = "DOCK",
[EXTCON_JIG] = "JIG",
[EXTCON_MECHANICAL] = "MECHANICAL",
NULL,
};
static struct class *extcon_class;
@ -102,6 +111,51 @@ static int check_mutually_exclusive(struct extcon_dev *edev, u32 new_state)
return 0;
}
static int find_cable_index_by_id(struct extcon_dev *edev, const unsigned int id)
{
int i;
/* Find the the index of extcon cable in edev->supported_cable */
for (i = 0; i < edev->max_supported; i++) {
if (edev->supported_cable[i] == id)
return i;
}
return -EINVAL;
}
static int find_cable_index_by_name(struct extcon_dev *edev, const char *name)
{
unsigned int id = EXTCON_NONE;
int i = 0;
if (edev->max_supported == 0)
return -EINVAL;
/* Find the the number of extcon cable */
while (extcon_name[i]) {
if (!strncmp(extcon_name[i], name, CABLE_NAME_MAX)) {
id = i;
break;
}
}
if (id == EXTCON_NONE)
return -EINVAL;
return find_cable_index_by_id(edev, id);
}
static bool is_extcon_changed(u32 prev, u32 new, int idx, bool *attached)
{
if (((prev >> idx) & 0x1) != ((new >> idx) & 0x1)) {
*attached = new ? true : false;
return true;
}
return false;
}
static ssize_t state_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
@ -119,11 +173,9 @@ static ssize_t state_show(struct device *dev, struct device_attribute *attr,
if (edev->max_supported == 0)
return sprintf(buf, "%u\n", edev->state);
for (i = 0; i < SUPPORTED_CABLE_MAX; i++) {
if (!edev->supported_cable[i])
break;
for (i = 0; i < edev->max_supported; i++) {
count += sprintf(buf + count, "%s=%d\n",
edev->supported_cable[i],
extcon_name[edev->supported_cable[i]],
!!(edev->state & (1 << i)));
}
@ -155,15 +207,7 @@ static ssize_t name_show(struct device *dev, struct device_attribute *attr,
{
struct extcon_dev *edev = dev_get_drvdata(dev);
/* Optional callback given by the user */
if (edev->print_name) {
int ret = edev->print_name(edev, buf);
if (ret >= 0)
return ret;
}
return sprintf(buf, "%s\n", dev_name(&edev->dev));
return sprintf(buf, "%s\n", edev->name);
}
static DEVICE_ATTR_RO(name);
@ -172,9 +216,10 @@ static ssize_t cable_name_show(struct device *dev,
{
struct extcon_cable *cable = container_of(attr, struct extcon_cable,
attr_name);
int i = cable->cable_index;
return sprintf(buf, "%s\n",
cable->edev->supported_cable[cable->cable_index]);
extcon_name[cable->edev->supported_cable[i]]);
}
static ssize_t cable_state_show(struct device *dev,
@ -211,23 +256,27 @@ int extcon_update_state(struct extcon_dev *edev, u32 mask, u32 state)
char *envp[3];
int env_offset = 0;
int length;
int index;
unsigned long flags;
bool attached;
spin_lock_irqsave(&edev->lock, flags);
if (edev->state != ((edev->state & ~mask) | (state & mask))) {
u32 old_state = edev->state;
if (check_mutually_exclusive(edev, (edev->state & ~mask) |
(state & mask))) {
spin_unlock_irqrestore(&edev->lock, flags);
return -EPERM;
}
for (index = 0; index < edev->max_supported; index++) {
if (is_extcon_changed(edev->state, state, index, &attached))
raw_notifier_call_chain(&edev->nh[index], attached, edev);
}
edev->state &= ~mask;
edev->state |= state & mask;
raw_notifier_call_chain(&edev->nh, old_state, edev);
/* This could be in interrupt handler */
prop_buf = (char *)get_zeroed_page(GFP_ATOMIC);
if (prop_buf) {
@ -283,40 +332,20 @@ int extcon_set_state(struct extcon_dev *edev, u32 state)
}
EXPORT_SYMBOL_GPL(extcon_set_state);
/**
* extcon_find_cable_index() - Get the cable index based on the cable name.
* @edev: the extcon device that has the cable.
* @cable_name: cable name to be searched.
*
* Note that accessing a cable state based on cable_index is faster than
* cable_name because using cable_name induces a loop with strncmp().
* Thus, when get/set_cable_state is repeatedly used, using cable_index
* is recommended.
*/
int extcon_find_cable_index(struct extcon_dev *edev, const char *cable_name)
{
int i;
if (edev->supported_cable) {
for (i = 0; edev->supported_cable[i]; i++) {
if (!strncmp(edev->supported_cable[i],
cable_name, CABLE_NAME_MAX))
return i;
}
}
return -EINVAL;
}
EXPORT_SYMBOL_GPL(extcon_find_cable_index);
/**
* extcon_get_cable_state_() - Get the status of a specific cable.
* @edev: the extcon device that has the cable.
* @index: cable index that can be retrieved by extcon_find_cable_index().
* @id: the unique id of each external connector in extcon enumeration.
*/
int extcon_get_cable_state_(struct extcon_dev *edev, int index)
int extcon_get_cable_state_(struct extcon_dev *edev, const unsigned int id)
{
if (index < 0 || (edev->max_supported && edev->max_supported <= index))
int index;
index = find_cable_index_by_id(edev, id);
if (index < 0)
return index;
if (edev->max_supported && edev->max_supported <= index)
return -EINVAL;
return !!(edev->state & (1 << index));
@ -332,7 +361,7 @@ EXPORT_SYMBOL_GPL(extcon_get_cable_state_);
*/
int extcon_get_cable_state(struct extcon_dev *edev, const char *cable_name)
{
return extcon_get_cable_state_(edev, extcon_find_cable_index
return extcon_get_cable_state_(edev, find_cable_index_by_name
(edev, cable_name));
}
EXPORT_SYMBOL_GPL(extcon_get_cable_state);
@ -340,17 +369,22 @@ EXPORT_SYMBOL_GPL(extcon_get_cable_state);
/**
* extcon_set_cable_state_() - Set the status of a specific cable.
* @edev: the extcon device that has the cable.
* @index: cable index that can be retrieved by
* extcon_find_cable_index().
* @cable_state: the new cable status. The default semantics is
* @id: the unique id of each external connector
* in extcon enumeration.
* @state: the new cable status. The default semantics is
* true: attached / false: detached.
*/
int extcon_set_cable_state_(struct extcon_dev *edev,
int index, bool cable_state)
int extcon_set_cable_state_(struct extcon_dev *edev, unsigned int id,
bool cable_state)
{
u32 state;
int index;
if (index < 0 || (edev->max_supported && edev->max_supported <= index))
index = find_cable_index_by_id(edev, id);
if (index < 0)
return index;
if (edev->max_supported && edev->max_supported <= index)
return -EINVAL;
state = cable_state ? (1 << index) : 0;
@ -370,7 +404,7 @@ EXPORT_SYMBOL_GPL(extcon_set_cable_state_);
int extcon_set_cable_state(struct extcon_dev *edev,
const char *cable_name, bool cable_state)
{
return extcon_set_cable_state_(edev, extcon_find_cable_index
return extcon_set_cable_state_(edev, find_cable_index_by_name
(edev, cable_name), cable_state);
}
EXPORT_SYMBOL_GPL(extcon_set_cable_state);
@ -395,29 +429,6 @@ out:
}
EXPORT_SYMBOL_GPL(extcon_get_extcon_dev);
static int _call_per_cable(struct notifier_block *nb, unsigned long val,
void *ptr)
{
struct extcon_specific_cable_nb *obj = container_of(nb,
struct extcon_specific_cable_nb, internal_nb);
struct extcon_dev *edev = ptr;
if ((val & (1 << obj->cable_index)) !=
(edev->state & (1 << obj->cable_index))) {
bool cable_state = true;
obj->previous_value = val;
if (val & (1 << obj->cable_index))
cable_state = false;
return obj->user_nb->notifier_call(obj->user_nb,
cable_state, ptr);
}
return NOTIFY_OK;
}
/**
* extcon_register_interest() - Register a notifier for a state change of a
* specific cable, not an entier set of cables of a
@ -456,20 +467,18 @@ int extcon_register_interest(struct extcon_specific_cable_nb *obj,
if (!obj->edev)
return -ENODEV;
obj->cable_index = extcon_find_cable_index(obj->edev,
cable_name);
obj->cable_index = find_cable_index_by_name(obj->edev,
cable_name);
if (obj->cable_index < 0)
return obj->cable_index;
obj->user_nb = nb;
obj->internal_nb.notifier_call = _call_per_cable;
spin_lock_irqsave(&obj->edev->lock, flags);
ret = raw_notifier_chain_register(&obj->edev->nh,
&obj->internal_nb);
ret = raw_notifier_chain_register(
&obj->edev->nh[obj->cable_index],
obj->user_nb);
spin_unlock_irqrestore(&obj->edev->lock, flags);
return ret;
} else {
struct class_dev_iter iter;
struct extcon_dev *extd;
@ -481,7 +490,7 @@ int extcon_register_interest(struct extcon_specific_cable_nb *obj,
while ((dev = class_dev_iter_next(&iter))) {
extd = dev_get_drvdata(dev);
if (extcon_find_cable_index(extd, cable_name) < 0)
if (find_cable_index_by_name(extd, cable_name) < 0)
continue;
class_dev_iter_exit(&iter);
@ -489,8 +498,10 @@ int extcon_register_interest(struct extcon_specific_cable_nb *obj,
cable_name, nb);
}
return -ENODEV;
ret = -ENODEV;
}
return ret;
}
EXPORT_SYMBOL_GPL(extcon_register_interest);
@ -509,7 +520,8 @@ int extcon_unregister_interest(struct extcon_specific_cable_nb *obj)
return -EINVAL;
spin_lock_irqsave(&obj->edev->lock, flags);
ret = raw_notifier_chain_unregister(&obj->edev->nh, &obj->internal_nb);
ret = raw_notifier_chain_unregister(
&obj->edev->nh[obj->cable_index], obj->user_nb);
spin_unlock_irqrestore(&obj->edev->lock, flags);
return ret;
@ -519,21 +531,24 @@ EXPORT_SYMBOL_GPL(extcon_unregister_interest);
/**
* extcon_register_notifier() - Register a notifiee to get notified by
* any attach status changes from the extcon.
* @edev: the extcon device.
* @edev: the extcon device that has the external connecotr.
* @id: the unique id of each external connector in extcon enumeration.
* @nb: a notifier block to be registered.
*
* Note that the second parameter given to the callback of nb (val) is
* "old_state", not the current state. The current state can be retrieved
* by looking at the third pameter (edev pointer)'s state value.
*/
int extcon_register_notifier(struct extcon_dev *edev,
struct notifier_block *nb)
int extcon_register_notifier(struct extcon_dev *edev, unsigned int id,
struct notifier_block *nb)
{
unsigned long flags;
int ret;
int ret, idx;
idx = find_cable_index_by_id(edev, id);
spin_lock_irqsave(&edev->lock, flags);
ret = raw_notifier_chain_register(&edev->nh, nb);
ret = raw_notifier_chain_register(&edev->nh[idx], nb);
spin_unlock_irqrestore(&edev->lock, flags);
return ret;
@ -542,17 +557,20 @@ EXPORT_SYMBOL_GPL(extcon_register_notifier);
/**
* extcon_unregister_notifier() - Unregister a notifiee from the extcon device.
* @edev: the extcon device.
* @nb: a registered notifier block to be unregistered.
* @edev: the extcon device that has the external connecotr.
* @id: the unique id of each external connector in extcon enumeration.
* @nb: a notifier block to be registered.
*/
int extcon_unregister_notifier(struct extcon_dev *edev,
struct notifier_block *nb)
int extcon_unregister_notifier(struct extcon_dev *edev, unsigned int id,
struct notifier_block *nb)
{
unsigned long flags;
int ret;
int ret, idx;
idx = find_cable_index_by_id(edev, id);
spin_lock_irqsave(&edev->lock, flags);
ret = raw_notifier_chain_unregister(&edev->nh, nb);
ret = raw_notifier_chain_unregister(&edev->nh[idx], nb);
spin_unlock_irqrestore(&edev->lock, flags);
return ret;
@ -595,7 +613,7 @@ static void dummy_sysfs_dev_release(struct device *dev)
/*
* extcon_dev_allocate() - Allocate the memory of extcon device.
* @supported_cable: Array of supported cable names ending with NULL.
* @supported_cable: Array of supported extcon ending with EXTCON_NONE.
* If supported_cable is NULL, cable name related APIs
* are disabled.
*
@ -605,7 +623,7 @@ static void dummy_sysfs_dev_release(struct device *dev)
*
* Return the pointer of extcon device if success or ERR_PTR(err) if fail
*/
struct extcon_dev *extcon_dev_allocate(const char **supported_cable)
struct extcon_dev *extcon_dev_allocate(const unsigned int *supported_cable)
{
struct extcon_dev *edev;
@ -647,7 +665,7 @@ static void devm_extcon_dev_release(struct device *dev, void *res)
/**
* devm_extcon_dev_allocate - Allocate managed extcon device
* @dev: device owning the extcon device being created
* @supported_cable: Array of supported cable names ending with NULL.
* @supported_cable: Array of supported extcon ending with EXTCON_NONE.
* If supported_cable is NULL, cable name related APIs
* are disabled.
*
@ -659,7 +677,7 @@ static void devm_extcon_dev_release(struct device *dev, void *res)
* or ERR_PTR(err) if fail
*/
struct extcon_dev *devm_extcon_dev_allocate(struct device *dev,
const char **supported_cable)
const unsigned int *supported_cable)
{
struct extcon_dev **ptr, *edev;
@ -701,6 +719,7 @@ EXPORT_SYMBOL_GPL(devm_extcon_dev_free);
int extcon_dev_register(struct extcon_dev *edev)
{
int ret, index = 0;
static atomic_t edev_no = ATOMIC_INIT(-1);
if (!extcon_class) {
ret = create_extcon_class();
@ -708,30 +727,29 @@ int extcon_dev_register(struct extcon_dev *edev)
return ret;
}
if (edev->supported_cable) {
/* Get size of array */
for (index = 0; edev->supported_cable[index]; index++)
;
edev->max_supported = index;
} else {
edev->max_supported = 0;
}
if (!edev->supported_cable)
return -EINVAL;
for (; edev->supported_cable[index] != EXTCON_NONE; index++);
edev->max_supported = index;
if (index > SUPPORTED_CABLE_MAX) {
dev_err(&edev->dev, "extcon: maximum number of supported cables exceeded.\n");
dev_err(&edev->dev,
"exceed the maximum number of supported cables\n");
return -EINVAL;
}
edev->dev.class = extcon_class;
edev->dev.release = extcon_dev_release;
edev->name = edev->name ? edev->name : dev_name(edev->dev.parent);
edev->name = dev_name(edev->dev.parent);
if (IS_ERR_OR_NULL(edev->name)) {
dev_err(&edev->dev,
"extcon device name is null\n");
return -EINVAL;
}
dev_set_name(&edev->dev, "%s", edev->name);
dev_set_name(&edev->dev, "extcon%lu",
(unsigned long)atomic_inc_return(&edev_no));
if (edev->max_supported) {
char buf[10];
@ -864,7 +882,15 @@ int extcon_dev_register(struct extcon_dev *edev)
spin_lock_init(&edev->lock);
RAW_INIT_NOTIFIER_HEAD(&edev->nh);
edev->nh = devm_kzalloc(&edev->dev,
sizeof(*edev->nh) * edev->max_supported, GFP_KERNEL);
if (!edev->nh) {
ret = -ENOMEM;
goto err_dev;
}
for (index = 0; index < edev->max_supported; index++)
RAW_INIT_NOTIFIER_HEAD(&edev->nh[index]);
dev_set_drvdata(&edev->dev, edev);
edev->state = 0;
@ -1044,6 +1070,15 @@ struct extcon_dev *extcon_get_edev_by_phandle(struct device *dev, int index)
#endif /* CONFIG_OF */
EXPORT_SYMBOL_GPL(extcon_get_edev_by_phandle);
/**
* extcon_get_edev_name() - Get the name of the extcon device.
* @edev: the extcon device
*/
const char *extcon_get_edev_name(struct extcon_dev *edev)
{
return !edev ? NULL : edev->name;
}
static int __init extcon_class_init(void)
{
return create_extcon_class();
@ -1059,6 +1094,7 @@ static void __exit extcon_class_exit(void)
}
module_exit(extcon_class_exit);
MODULE_AUTHOR("Chanwoo Choi <cw00.choi@samsung.com>");
MODULE_AUTHOR("Mike Lockwood <lockwood@android.com>");
MODULE_AUTHOR("Donggeun Kim <dg77.kim@samsung.com>");
MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>");

View File

@ -5,4 +5,4 @@ obj-$(CONFIG_HYPERV_BALLOON) += hv_balloon.o
hv_vmbus-y := vmbus_drv.o \
hv.o connection.o channel.o \
channel_mgmt.o ring_buffer.o
hv_utils-y := hv_util.o hv_kvp.o hv_snapshot.o hv_fcopy.o
hv_utils-y := hv_util.o hv_kvp.o hv_snapshot.o hv_fcopy.o hv_utils_transport.o

View File

@ -73,6 +73,7 @@ int vmbus_open(struct vmbus_channel *newchannel, u32 send_ringbuffer_size,
unsigned long flags;
int ret, err = 0;
unsigned long t;
struct page *page;
spin_lock_irqsave(&newchannel->lock, flags);
if (newchannel->state == CHANNEL_OPEN_STATE) {
@ -87,8 +88,17 @@ int vmbus_open(struct vmbus_channel *newchannel, u32 send_ringbuffer_size,
newchannel->channel_callback_context = context;
/* Allocate the ring buffer */
out = (void *)__get_free_pages(GFP_KERNEL|__GFP_ZERO,
get_order(send_ringbuffer_size + recv_ringbuffer_size));
page = alloc_pages_node(cpu_to_node(newchannel->target_cpu),
GFP_KERNEL|__GFP_ZERO,
get_order(send_ringbuffer_size +
recv_ringbuffer_size));
if (!page)
out = (void *)__get_free_pages(GFP_KERNEL|__GFP_ZERO,
get_order(send_ringbuffer_size +
recv_ringbuffer_size));
else
out = (void *)page_address(page);
if (!out) {
err = -ENOMEM;
@ -178,19 +188,18 @@ int vmbus_open(struct vmbus_channel *newchannel, u32 send_ringbuffer_size,
goto error1;
}
if (open_info->response.open_result.status)
err = open_info->response.open_result.status;
spin_lock_irqsave(&vmbus_connection.channelmsg_lock, flags);
list_del(&open_info->msglistentry);
spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock, flags);
if (err == 0)
newchannel->state = CHANNEL_OPENED_STATE;
if (open_info->response.open_result.status) {
err = -EAGAIN;
goto error_gpadl;
}
newchannel->state = CHANNEL_OPENED_STATE;
kfree(open_info);
return err;
return 0;
error1:
spin_lock_irqsave(&vmbus_connection.channelmsg_lock, flags);

View File

@ -32,6 +32,9 @@
#include "hyperv_vmbus.h"
static void init_vp_index(struct vmbus_channel *channel,
const uuid_le *type_guid);
/**
* vmbus_prep_negotiate_resp() - Create default response for Hyper-V Negotiate message
* @icmsghdrp: Pointer to msg header structure
@ -205,6 +208,7 @@ void hv_process_channel_removal(struct vmbus_channel *channel, u32 relid)
primary_channel = channel->primary_channel;
spin_lock_irqsave(&primary_channel->lock, flags);
list_del(&channel->sc_list);
primary_channel->num_sc--;
spin_unlock_irqrestore(&primary_channel->lock, flags);
}
free_channel(channel);
@ -212,11 +216,16 @@ void hv_process_channel_removal(struct vmbus_channel *channel, u32 relid)
void vmbus_free_channels(void)
{
struct vmbus_channel *channel;
struct vmbus_channel *channel, *tmp;
list_for_each_entry_safe(channel, tmp, &vmbus_connection.chn_list,
listentry) {
/* if we don't set rescind to true, vmbus_close_internal()
* won't invoke hv_process_channel_removal().
*/
channel->rescind = true;
list_for_each_entry(channel, &vmbus_connection.chn_list, listentry) {
vmbus_device_unregister(channel->device_obj);
free_channel(channel);
}
}
@ -228,7 +237,6 @@ static void vmbus_process_offer(struct vmbus_channel *newchannel)
{
struct vmbus_channel *channel;
bool fnew = true;
bool enq = false;
unsigned long flags;
/* Make sure this is a new offer */
@ -244,25 +252,12 @@ static void vmbus_process_offer(struct vmbus_channel *newchannel)
}
}
if (fnew) {
if (fnew)
list_add_tail(&newchannel->listentry,
&vmbus_connection.chn_list);
enq = true;
}
spin_unlock_irqrestore(&vmbus_connection.channel_lock, flags);
if (enq) {
if (newchannel->target_cpu != get_cpu()) {
put_cpu();
smp_call_function_single(newchannel->target_cpu,
percpu_channel_enq,
newchannel, true);
} else {
percpu_channel_enq(newchannel);
put_cpu();
}
}
if (!fnew) {
/*
* Check to see if this is a sub-channel.
@ -274,27 +269,22 @@ static void vmbus_process_offer(struct vmbus_channel *newchannel)
newchannel->primary_channel = channel;
spin_lock_irqsave(&channel->lock, flags);
list_add_tail(&newchannel->sc_list, &channel->sc_list);
spin_unlock_irqrestore(&channel->lock, flags);
if (newchannel->target_cpu != get_cpu()) {
put_cpu();
smp_call_function_single(newchannel->target_cpu,
percpu_channel_enq,
newchannel, true);
} else {
percpu_channel_enq(newchannel);
put_cpu();
}
newchannel->state = CHANNEL_OPEN_STATE;
channel->num_sc++;
if (channel->sc_creation_callback != NULL)
channel->sc_creation_callback(newchannel);
spin_unlock_irqrestore(&channel->lock, flags);
} else
goto err_free_chan;
}
return;
}
init_vp_index(newchannel, &newchannel->offermsg.offer.if_type);
goto err_free_chan;
if (newchannel->target_cpu != get_cpu()) {
put_cpu();
smp_call_function_single(newchannel->target_cpu,
percpu_channel_enq,
newchannel, true);
} else {
percpu_channel_enq(newchannel);
put_cpu();
}
/*
@ -304,6 +294,12 @@ static void vmbus_process_offer(struct vmbus_channel *newchannel)
*/
newchannel->state = CHANNEL_OPEN_STATE;
if (!fnew) {
if (channel->sc_creation_callback != NULL)
channel->sc_creation_callback(newchannel);
return;
}
/*
* Start the process of binding this offer to the driver
* We need to set the DeviceObject field before calling
@ -374,23 +370,27 @@ static const struct hv_vmbus_device_id hp_devs[] = {
/*
* We use this state to statically distribute the channel interrupt load.
*/
static u32 next_vp;
static int next_numa_node_id;
/*
* Starting with Win8, we can statically distribute the incoming
* channel interrupt load by binding a channel to VCPU. We
* implement here a simple round robin scheme for distributing
* the interrupt load.
* We will bind channels that are not performance critical to cpu 0 and
* performance critical channels (IDE, SCSI and Network) will be uniformly
* distributed across all available CPUs.
* channel interrupt load by binding a channel to VCPU.
* We do this in a hierarchical fashion:
* First distribute the primary channels across available NUMA nodes
* and then distribute the subchannels amongst the CPUs in the NUMA
* node assigned to the primary channel.
*
* For pre-win8 hosts or non-performance critical channels we assign the
* first CPU in the first NUMA node.
*/
static void init_vp_index(struct vmbus_channel *channel, const uuid_le *type_guid)
{
u32 cur_cpu;
int i;
bool perf_chn = false;
u32 max_cpus = num_online_cpus();
struct vmbus_channel *primary = channel->primary_channel;
int next_node;
struct cpumask available_mask;
for (i = IDE; i < MAX_PERF_CHN; i++) {
if (!memcmp(type_guid->b, hp_devs[i].guid,
@ -407,15 +407,76 @@ static void init_vp_index(struct vmbus_channel *channel, const uuid_le *type_gui
* Also if the channel is not a performance critical
* channel, bind it to cpu 0.
*/
channel->numa_node = 0;
cpumask_set_cpu(0, &channel->alloced_cpus_in_node);
channel->target_cpu = 0;
channel->target_vp = 0;
channel->target_vp = hv_context.vp_index[0];
return;
}
cur_cpu = (++next_vp % max_cpus);
/*
* We distribute primary channels evenly across all the available
* NUMA nodes and within the assigned NUMA node we will assign the
* first available CPU to the primary channel.
* The sub-channels will be assigned to the CPUs available in the
* NUMA node evenly.
*/
if (!primary) {
while (true) {
next_node = next_numa_node_id++;
if (next_node == nr_node_ids)
next_node = next_numa_node_id = 0;
if (cpumask_empty(cpumask_of_node(next_node)))
continue;
break;
}
channel->numa_node = next_node;
primary = channel;
}
if (cpumask_weight(&primary->alloced_cpus_in_node) ==
cpumask_weight(cpumask_of_node(primary->numa_node))) {
/*
* We have cycled through all the CPUs in the node;
* reset the alloced map.
*/
cpumask_clear(&primary->alloced_cpus_in_node);
}
cpumask_xor(&available_mask, &primary->alloced_cpus_in_node,
cpumask_of_node(primary->numa_node));
cur_cpu = cpumask_next(-1, &available_mask);
cpumask_set_cpu(cur_cpu, &primary->alloced_cpus_in_node);
channel->target_cpu = cur_cpu;
channel->target_vp = hv_context.vp_index[cur_cpu];
}
/*
* vmbus_unload_response - Handler for the unload response.
*/
static void vmbus_unload_response(struct vmbus_channel_message_header *hdr)
{
/*
* This is a global event; just wakeup the waiting thread.
* Once we successfully unload, we can cleanup the monitor state.
*/
complete(&vmbus_connection.unload_event);
}
void vmbus_initiate_unload(void)
{
struct vmbus_channel_message_header hdr;
init_completion(&vmbus_connection.unload_event);
memset(&hdr, 0, sizeof(struct vmbus_channel_message_header));
hdr.msgtype = CHANNELMSG_UNLOAD;
vmbus_post_msg(&hdr, sizeof(struct vmbus_channel_message_header));
wait_for_completion(&vmbus_connection.unload_event);
}
/*
* vmbus_onoffer - Handler for channel offers from vmbus in parent partition.
*
@ -461,8 +522,6 @@ static void vmbus_onoffer(struct vmbus_channel_message_header *hdr)
offer->connection_id;
}
init_vp_index(newchannel, &offer->offer.if_type);
memcpy(&newchannel->offermsg, offer,
sizeof(struct vmbus_channel_offer_channel));
newchannel->monitor_grp = (u8)offer->monitorid / 32;
@ -712,6 +771,7 @@ struct vmbus_channel_message_table_entry
{CHANNELMSG_INITIATE_CONTACT, 0, NULL},
{CHANNELMSG_VERSION_RESPONSE, 1, vmbus_onversion_response},
{CHANNELMSG_UNLOAD, 0, NULL},
{CHANNELMSG_UNLOAD_RESPONSE, 1, vmbus_unload_response},
};
/*

View File

@ -58,6 +58,9 @@ static __u32 vmbus_get_next_version(__u32 current_version)
case (VERSION_WIN8_1):
return VERSION_WIN8;
case (VERSION_WIN10):
return VERSION_WIN8_1;
case (VERSION_WS2008):
default:
return VERSION_INVAL;
@ -80,7 +83,7 @@ static int vmbus_negotiate_version(struct vmbus_channel_msginfo *msginfo,
msg->interrupt_page = virt_to_phys(vmbus_connection.int_page);
msg->monitor_page1 = virt_to_phys(vmbus_connection.monitor_pages[0]);
msg->monitor_page2 = virt_to_phys(vmbus_connection.monitor_pages[1]);
if (version == VERSION_WIN8_1) {
if (version >= VERSION_WIN8_1) {
msg->target_vcpu = hv_context.vp_index[get_cpu()];
put_cpu();
}
@ -227,6 +230,11 @@ cleanup:
void vmbus_disconnect(void)
{
/*
* First send the unload request to the host.
*/
vmbus_initiate_unload();
if (vmbus_connection.work_queue) {
drain_workqueue(vmbus_connection.work_queue);
destroy_workqueue(vmbus_connection.work_queue);
@ -371,8 +379,7 @@ void vmbus_on_event(unsigned long data)
int cpu = smp_processor_id();
union hv_synic_event_flags *event;
if ((vmbus_proto_version == VERSION_WS2008) ||
(vmbus_proto_version == VERSION_WIN7)) {
if (vmbus_proto_version < VERSION_WIN8) {
maxdword = MAX_NUM_CHANNELS_SUPPORTED >> 5;
recv_int_page = vmbus_connection.recv_int_page;
} else {

View File

@ -567,7 +567,9 @@ static int hv_memory_notifier(struct notifier_block *nb, unsigned long val,
case MEM_ONLINE:
dm_device.num_pages_onlined += mem->nr_pages;
case MEM_CANCEL_ONLINE:
mutex_unlock(&dm_device.ha_region_mutex);
if (val == MEM_ONLINE ||
mutex_is_locked(&dm_device.ha_region_mutex))
mutex_unlock(&dm_device.ha_region_mutex);
if (dm_device.ha_waiting) {
dm_device.ha_waiting = false;
complete(&dm_device.ol_waitevent);

View File

@ -19,17 +19,13 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/semaphore.h>
#include <linux/fs.h>
#include <linux/nls.h>
#include <linux/workqueue.h>
#include <linux/cdev.h>
#include <linux/hyperv.h>
#include <linux/sched.h>
#include <linux/uaccess.h>
#include <linux/miscdevice.h>
#include "hyperv_vmbus.h"
#include "hv_utils_transport.h"
#define WIN8_SRV_MAJOR 1
#define WIN8_SRV_MINOR 1
@ -47,39 +43,31 @@
* ensure this by serializing packet processing in this driver - we do not
* read additional packets from the VMBUs until the current packet is fully
* handled.
*
* The transaction "active" state is set when we receive a request from the
* host and we cleanup this state when the transaction is completed - when we
* respond to the host with our response. When the transaction active state is
* set, we defer handling incoming packets.
*/
static struct {
bool active; /* transaction status - active or not */
int state; /* hvutil_device_state */
int recv_len; /* number of bytes received. */
struct hv_fcopy_hdr *fcopy_msg; /* current message */
struct hv_start_fcopy message; /* sent to daemon */
struct vmbus_channel *recv_channel; /* chn we got the request */
u64 recv_req_id; /* request ID. */
void *fcopy_context; /* for the channel callback */
struct semaphore read_sema;
} fcopy_transaction;
static bool opened; /* currently device opened */
/*
* Before we can accept copy messages from the host, we need
* to handshake with the user level daemon. This state tracks
* if we are in the handshake phase.
*/
static bool in_hand_shake = true;
static void fcopy_send_data(void);
static void fcopy_respond_to_host(int error);
static void fcopy_work_func(struct work_struct *dummy);
static DECLARE_DELAYED_WORK(fcopy_work, fcopy_work_func);
static void fcopy_send_data(struct work_struct *dummy);
static void fcopy_timeout_func(struct work_struct *dummy);
static DECLARE_DELAYED_WORK(fcopy_timeout_work, fcopy_timeout_func);
static DECLARE_WORK(fcopy_send_work, fcopy_send_data);
static const char fcopy_devname[] = "vmbus/hv_fcopy";
static u8 *recv_buffer;
static struct hvutil_transport *hvt;
/*
* This state maintains the version number registered by the daemon.
*/
static int dm_reg_value;
static void fcopy_work_func(struct work_struct *dummy)
static void fcopy_timeout_func(struct work_struct *dummy)
{
/*
* If the timer fires, the user-mode component has not responded;
@ -87,23 +75,28 @@ static void fcopy_work_func(struct work_struct *dummy)
*/
fcopy_respond_to_host(HV_E_FAIL);
/* In the case the user-space daemon crashes, hangs or is killed, we
* need to down the semaphore, otherwise, after the daemon starts next
* time, the obsolete data in fcopy_transaction.message or
* fcopy_transaction.fcopy_msg will be used immediately.
*
* NOTE: fcopy_read() happens to get the semaphore (very rare)? We're
* still OK, because we've reported the failure to the host.
*/
if (down_trylock(&fcopy_transaction.read_sema))
;
/* Transaction is finished, reset the state. */
if (fcopy_transaction.state > HVUTIL_READY)
fcopy_transaction.state = HVUTIL_READY;
hv_poll_channel(fcopy_transaction.fcopy_context,
hv_fcopy_onchannelcallback);
}
static int fcopy_handle_handshake(u32 version)
{
u32 our_ver = FCOPY_CURRENT_VERSION;
switch (version) {
case FCOPY_CURRENT_VERSION:
case FCOPY_VERSION_0:
/* Daemon doesn't expect us to reply */
dm_reg_value = version;
break;
case FCOPY_VERSION_1:
/* Daemon expects us to reply with our own version */
if (hvutil_transport_send(hvt, &our_ver, sizeof(our_ver)))
return -EFAULT;
dm_reg_value = version;
break;
default:
/*
@ -114,20 +107,20 @@ static int fcopy_handle_handshake(u32 version)
*/
return -EINVAL;
}
pr_info("FCP: user-mode registering done. Daemon version: %d\n",
version);
fcopy_transaction.active = false;
if (fcopy_transaction.fcopy_context)
hv_fcopy_onchannelcallback(fcopy_transaction.fcopy_context);
in_hand_shake = false;
pr_debug("FCP: userspace daemon ver. %d registered\n", version);
fcopy_transaction.state = HVUTIL_READY;
hv_poll_channel(fcopy_transaction.fcopy_context,
hv_fcopy_onchannelcallback);
return 0;
}
static void fcopy_send_data(void)
static void fcopy_send_data(struct work_struct *dummy)
{
struct hv_start_fcopy *smsg_out = &fcopy_transaction.message;
struct hv_start_fcopy smsg_out;
int operation = fcopy_transaction.fcopy_msg->operation;
struct hv_start_fcopy *smsg_in;
void *out_src;
int rc, out_len;
/*
* The strings sent from the host are encoded in
@ -142,26 +135,39 @@ static void fcopy_send_data(void)
switch (operation) {
case START_FILE_COPY:
memset(smsg_out, 0, sizeof(struct hv_start_fcopy));
smsg_out->hdr.operation = operation;
out_len = sizeof(struct hv_start_fcopy);
memset(&smsg_out, 0, out_len);
smsg_out.hdr.operation = operation;
smsg_in = (struct hv_start_fcopy *)fcopy_transaction.fcopy_msg;
utf16s_to_utf8s((wchar_t *)smsg_in->file_name, W_MAX_PATH,
UTF16_LITTLE_ENDIAN,
(__u8 *)smsg_out->file_name, W_MAX_PATH - 1);
(__u8 *)&smsg_out.file_name, W_MAX_PATH - 1);
utf16s_to_utf8s((wchar_t *)smsg_in->path_name, W_MAX_PATH,
UTF16_LITTLE_ENDIAN,
(__u8 *)smsg_out->path_name, W_MAX_PATH - 1);
(__u8 *)&smsg_out.path_name, W_MAX_PATH - 1);
smsg_out->copy_flags = smsg_in->copy_flags;
smsg_out->file_size = smsg_in->file_size;
smsg_out.copy_flags = smsg_in->copy_flags;
smsg_out.file_size = smsg_in->file_size;
out_src = &smsg_out;
break;
default:
out_src = fcopy_transaction.fcopy_msg;
out_len = fcopy_transaction.recv_len;
break;
}
up(&fcopy_transaction.read_sema);
fcopy_transaction.state = HVUTIL_USERSPACE_REQ;
rc = hvutil_transport_send(hvt, out_src, out_len);
if (rc) {
pr_debug("FCP: failed to communicate to the daemon: %d\n", rc);
if (cancel_delayed_work_sync(&fcopy_timeout_work)) {
fcopy_respond_to_host(HV_E_FAIL);
fcopy_transaction.state = HVUTIL_READY;
}
}
return;
}
@ -189,8 +195,6 @@ fcopy_respond_to_host(int error)
channel = fcopy_transaction.recv_channel;
req_id = fcopy_transaction.recv_req_id;
fcopy_transaction.active = false;
icmsghdr = (struct icmsg_hdr *)
&recv_buffer[sizeof(struct vmbuspipe_hdr)];
@ -218,7 +222,7 @@ void hv_fcopy_onchannelcallback(void *context)
int util_fw_version;
int fcopy_srv_version;
if (fcopy_transaction.active) {
if (fcopy_transaction.state > HVUTIL_READY) {
/*
* We will defer processing this callback once
* the current transaction is complete.
@ -226,6 +230,7 @@ void hv_fcopy_onchannelcallback(void *context)
fcopy_transaction.fcopy_context = context;
return;
}
fcopy_transaction.fcopy_context = NULL;
vmbus_recvpacket(channel, recv_buffer, PAGE_SIZE * 2, &recvlen,
&requestid);
@ -249,17 +254,23 @@ void hv_fcopy_onchannelcallback(void *context)
* transaction; note transactions are serialized.
*/
fcopy_transaction.active = true;
fcopy_transaction.recv_len = recvlen;
fcopy_transaction.recv_channel = channel;
fcopy_transaction.recv_req_id = requestid;
fcopy_transaction.fcopy_msg = fcopy_msg;
if (fcopy_transaction.state < HVUTIL_READY) {
/* Userspace is not registered yet */
fcopy_respond_to_host(HV_E_FAIL);
return;
}
fcopy_transaction.state = HVUTIL_HOSTMSG_RECEIVED;
/*
* Send the information to the user-level daemon.
*/
schedule_delayed_work(&fcopy_work, 5*HZ);
fcopy_send_data();
schedule_work(&fcopy_send_work);
schedule_delayed_work(&fcopy_timeout_work, 5*HZ);
return;
}
icmsghdr->icflags = ICMSGHDRFLAG_TRANSACTION | ICMSGHDRFLAG_RESPONSE;
@ -267,155 +278,44 @@ void hv_fcopy_onchannelcallback(void *context)
VM_PKT_DATA_INBAND, 0);
}
/*
* Create a char device that can support read/write for passing
* the payload.
*/
static ssize_t fcopy_read(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
/* Callback when data is received from userspace */
static int fcopy_on_msg(void *msg, int len)
{
void *src;
size_t copy_size;
int operation;
int *val = (int *)msg;
/*
* Wait until there is something to be read.
*/
if (down_interruptible(&fcopy_transaction.read_sema))
return -EINTR;
/*
* The channel may be rescinded and in this case, we will wakeup the
* the thread blocked on the semaphore and we will use the opened
* state to correctly handle this case.
*/
if (!opened)
return -ENODEV;
operation = fcopy_transaction.fcopy_msg->operation;
if (operation == START_FILE_COPY) {
src = &fcopy_transaction.message;
copy_size = sizeof(struct hv_start_fcopy);
if (count < copy_size)
return 0;
} else {
src = fcopy_transaction.fcopy_msg;
copy_size = sizeof(struct hv_do_fcopy);
if (count < copy_size)
return 0;
}
if (copy_to_user(buf, src, copy_size))
return -EFAULT;
return copy_size;
}
static ssize_t fcopy_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
int response = 0;
if (count != sizeof(int))
if (len != sizeof(int))
return -EINVAL;
if (copy_from_user(&response, buf, sizeof(int)))
return -EFAULT;
if (fcopy_transaction.state == HVUTIL_DEVICE_INIT)
return fcopy_handle_handshake(*val);
if (in_hand_shake) {
if (fcopy_handle_handshake(response))
return -EINVAL;
return sizeof(int);
}
if (fcopy_transaction.state != HVUTIL_USERSPACE_REQ)
return -EINVAL;
/*
* Complete the transaction by forwarding the result
* to the host. But first, cancel the timeout.
*/
if (cancel_delayed_work_sync(&fcopy_work))
fcopy_respond_to_host(response);
if (cancel_delayed_work_sync(&fcopy_timeout_work)) {
fcopy_transaction.state = HVUTIL_USERSPACE_RECV;
fcopy_respond_to_host(*val);
fcopy_transaction.state = HVUTIL_READY;
hv_poll_channel(fcopy_transaction.fcopy_context,
hv_fcopy_onchannelcallback);
}
return sizeof(int);
}
static int fcopy_open(struct inode *inode, struct file *f)
{
/*
* The user level daemon that will open this device is
* really an extension of this driver. We can have only
* active open at a time.
*/
if (opened)
return -EBUSY;
/*
* The daemon is alive; setup the state.
*/
opened = true;
return 0;
}
/* XXX: there are still some tricky corner cases, e.g.,
* 1) In a SMP guest, when fcopy_release() runs between
* schedule_delayed_work() and fcopy_send_data(), there is
* still a chance an obsolete message will be queued.
*
* 2) When the fcopy daemon is running, if we unload the driver,
* we'll notice a kernel oops when we kill the daemon later.
*/
static int fcopy_release(struct inode *inode, struct file *f)
static void fcopy_on_reset(void)
{
/*
* The daemon has exited; reset the state.
*/
in_hand_shake = true;
opened = false;
fcopy_transaction.state = HVUTIL_DEVICE_INIT;
if (cancel_delayed_work_sync(&fcopy_work)) {
/* We haven't up()-ed the semaphore(very rare)? */
if (down_trylock(&fcopy_transaction.read_sema))
;
if (cancel_delayed_work_sync(&fcopy_timeout_work))
fcopy_respond_to_host(HV_E_FAIL);
}
return 0;
}
static const struct file_operations fcopy_fops = {
.read = fcopy_read,
.write = fcopy_write,
.release = fcopy_release,
.open = fcopy_open,
};
static struct miscdevice fcopy_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = "vmbus/hv_fcopy",
.fops = &fcopy_fops,
};
static int fcopy_dev_init(void)
{
return misc_register(&fcopy_misc);
}
static void fcopy_dev_deinit(void)
{
/*
* The device is going away - perhaps because the
* host has rescinded the channel. Setup state so that
* user level daemon can gracefully exit if it is blocked
* on the read semaphore.
*/
opened = false;
/*
* Signal the semaphore as the device is
* going away.
*/
up(&fcopy_transaction.read_sema);
misc_deregister(&fcopy_misc);
}
int hv_fcopy_init(struct hv_util_service *srv)
@ -428,14 +328,19 @@ int hv_fcopy_init(struct hv_util_service *srv)
* Defer processing channel callbacks until the daemon
* has registered.
*/
fcopy_transaction.active = true;
sema_init(&fcopy_transaction.read_sema, 0);
fcopy_transaction.state = HVUTIL_DEVICE_INIT;
return fcopy_dev_init();
hvt = hvutil_transport_init(fcopy_devname, 0, 0,
fcopy_on_msg, fcopy_on_reset);
if (!hvt)
return -EFAULT;
return 0;
}
void hv_fcopy_deinit(void)
{
cancel_delayed_work_sync(&fcopy_work);
fcopy_dev_deinit();
fcopy_transaction.state = HVUTIL_DEVICE_DYING;
cancel_delayed_work_sync(&fcopy_timeout_work);
hvutil_transport_destroy(hvt);
}

View File

@ -28,6 +28,8 @@
#include <linux/workqueue.h>
#include <linux/hyperv.h>
#include "hyperv_vmbus.h"
#include "hv_utils_transport.h"
/*
* Pre win8 version numbers used in ws2008 and ws 2008 r2 (win7)
@ -45,16 +47,21 @@
#define WIN8_SRV_VERSION (WIN8_SRV_MAJOR << 16 | WIN8_SRV_MINOR)
/*
* Global state maintained for transaction that is being processed.
* Note that only one transaction can be active at any point in time.
* Global state maintained for transaction that is being processed. For a class
* of integration services, including the "KVP service", the specified protocol
* is a "request/response" protocol which means that there can only be single
* outstanding transaction from the host at any given point in time. We use
* this to simplify memory management in this driver - we cache and process
* only one message at a time.
*
* This state is set when we receive a request from the host; we
* cleanup this state when the transaction is completed - when we respond
* to the host with the key value.
* While the request/response protocol is guaranteed by the host, we further
* ensure this by serializing packet processing in this driver - we do not
* read additional packets from the VMBUs until the current packet is fully
* handled.
*/
static struct {
bool active; /* transaction status - active or not */
int state; /* hvutil_device_state */
int recv_len; /* number of bytes received. */
struct hv_kvp_msg *kvp_msg; /* current message */
struct vmbus_channel *recv_channel; /* chn we got the request */
@ -62,13 +69,6 @@ static struct {
void *kvp_context; /* for the channel callback */
} kvp_transaction;
/*
* Before we can accept KVP messages from the host, we need
* to handshake with the user level daemon. This state tracks
* if we are in the handshake phase.
*/
static bool in_hand_shake = true;
/*
* This state maintains the version number registered by the daemon.
*/
@ -78,15 +78,15 @@ static void kvp_send_key(struct work_struct *dummy);
static void kvp_respond_to_host(struct hv_kvp_msg *msg, int error);
static void kvp_work_func(struct work_struct *dummy);
static void kvp_timeout_func(struct work_struct *dummy);
static void kvp_register(int);
static DECLARE_DELAYED_WORK(kvp_work, kvp_work_func);
static DECLARE_DELAYED_WORK(kvp_timeout_work, kvp_timeout_func);
static DECLARE_WORK(kvp_sendkey_work, kvp_send_key);
static struct cb_id kvp_id = { CN_KVP_IDX, CN_KVP_VAL };
static const char kvp_name[] = "kvp_kernel_module";
static const char kvp_devname[] = "vmbus/hv_kvp";
static u8 *recv_buffer;
static struct hvutil_transport *hvt;
/*
* Register the kernel component with the user-level daemon.
* As part of this registration, pass the LIC version number.
@ -98,50 +98,39 @@ static void
kvp_register(int reg_value)
{
struct cn_msg *msg;
struct hv_kvp_msg *kvp_msg;
char *version;
msg = kzalloc(sizeof(*msg) + sizeof(struct hv_kvp_msg), GFP_ATOMIC);
kvp_msg = kzalloc(sizeof(*kvp_msg), GFP_KERNEL);
if (msg) {
kvp_msg = (struct hv_kvp_msg *)msg->data;
if (kvp_msg) {
version = kvp_msg->body.kvp_register.version;
msg->id.idx = CN_KVP_IDX;
msg->id.val = CN_KVP_VAL;
kvp_msg->kvp_hdr.operation = reg_value;
strcpy(version, HV_DRV_VERSION);
msg->len = sizeof(struct hv_kvp_msg);
cn_netlink_send(msg, 0, 0, GFP_ATOMIC);
kfree(msg);
hvutil_transport_send(hvt, kvp_msg, sizeof(*kvp_msg));
kfree(kvp_msg);
}
}
static void
kvp_work_func(struct work_struct *dummy)
static void kvp_timeout_func(struct work_struct *dummy)
{
/*
* If the timer fires, the user-mode component has not responded;
* process the pending transaction.
*/
kvp_respond_to_host(NULL, HV_E_FAIL);
}
static void poll_channel(struct vmbus_channel *channel)
{
if (channel->target_cpu != smp_processor_id())
smp_call_function_single(channel->target_cpu,
hv_kvp_onchannelcallback,
channel, true);
else
hv_kvp_onchannelcallback(channel);
}
/* Transaction is finished, reset the state. */
if (kvp_transaction.state > HVUTIL_READY)
kvp_transaction.state = HVUTIL_READY;
hv_poll_channel(kvp_transaction.kvp_context,
hv_kvp_onchannelcallback);
}
static int kvp_handle_handshake(struct hv_kvp_msg *msg)
{
int ret = 1;
switch (msg->kvp_hdr.operation) {
case KVP_OP_REGISTER:
dm_reg_value = KVP_OP_REGISTER;
@ -155,20 +144,18 @@ static int kvp_handle_handshake(struct hv_kvp_msg *msg)
pr_info("KVP: incompatible daemon\n");
pr_info("KVP: KVP version: %d, Daemon version: %d\n",
KVP_OP_REGISTER1, msg->kvp_hdr.operation);
ret = 0;
return -EINVAL;
}
if (ret) {
/*
* We have a compatible daemon; complete the handshake.
*/
pr_info("KVP: user-mode registering done.\n");
kvp_register(dm_reg_value);
kvp_transaction.active = false;
if (kvp_transaction.kvp_context)
poll_channel(kvp_transaction.kvp_context);
}
return ret;
/*
* We have a compatible daemon; complete the handshake.
*/
pr_debug("KVP: userspace daemon ver. %d registered\n",
KVP_OP_REGISTER);
kvp_register(dm_reg_value);
kvp_transaction.state = HVUTIL_READY;
return 0;
}
@ -176,26 +163,30 @@ static int kvp_handle_handshake(struct hv_kvp_msg *msg)
* Callback when data is received from user mode.
*/
static void
kvp_cn_callback(struct cn_msg *msg, struct netlink_skb_parms *nsp)
static int kvp_on_msg(void *msg, int len)
{
struct hv_kvp_msg *message;
struct hv_kvp_msg *message = (struct hv_kvp_msg *)msg;
struct hv_kvp_msg_enumerate *data;
int error = 0;
message = (struct hv_kvp_msg *)msg->data;
if (len < sizeof(*message))
return -EINVAL;
/*
* If we are negotiating the version information
* with the daemon; handle that first.
*/
if (in_hand_shake) {
if (kvp_handle_handshake(message))
in_hand_shake = false;
return;
if (kvp_transaction.state < HVUTIL_READY) {
return kvp_handle_handshake(message);
}
/* We didn't send anything to userspace so the reply is spurious */
if (kvp_transaction.state < HVUTIL_USERSPACE_REQ)
return -EINVAL;
kvp_transaction.state = HVUTIL_USERSPACE_RECV;
/*
* Based on the version of the daemon, we propagate errors from the
* daemon differently.
@ -225,8 +216,14 @@ kvp_cn_callback(struct cn_msg *msg, struct netlink_skb_parms *nsp)
* Complete the transaction by forwarding the key value
* to the host. But first, cancel the timeout.
*/
if (cancel_delayed_work_sync(&kvp_work))
if (cancel_delayed_work_sync(&kvp_timeout_work)) {
kvp_respond_to_host(message, error);
kvp_transaction.state = HVUTIL_READY;
hv_poll_channel(kvp_transaction.kvp_context,
hv_kvp_onchannelcallback);
}
return 0;
}
@ -343,7 +340,6 @@ static void process_ib_ipinfo(void *in_msg, void *out_msg, int op)
static void
kvp_send_key(struct work_struct *dummy)
{
struct cn_msg *msg;
struct hv_kvp_msg *message;
struct hv_kvp_msg *in_msg;
__u8 operation = kvp_transaction.kvp_msg->kvp_hdr.operation;
@ -352,14 +348,11 @@ kvp_send_key(struct work_struct *dummy)
__u64 val64;
int rc;
msg = kzalloc(sizeof(*msg) + sizeof(struct hv_kvp_msg) , GFP_ATOMIC);
if (!msg)
/* The transaction state is wrong. */
if (kvp_transaction.state != HVUTIL_HOSTMSG_RECEIVED)
return;
msg->id.idx = CN_KVP_IDX;
msg->id.val = CN_KVP_VAL;
message = (struct hv_kvp_msg *)msg->data;
message = kzalloc(sizeof(*message), GFP_KERNEL);
message->kvp_hdr.operation = operation;
message->kvp_hdr.pool = pool;
in_msg = kvp_transaction.kvp_msg;
@ -446,15 +439,17 @@ kvp_send_key(struct work_struct *dummy)
break;
}
msg->len = sizeof(struct hv_kvp_msg);
rc = cn_netlink_send(msg, 0, 0, GFP_ATOMIC);
kvp_transaction.state = HVUTIL_USERSPACE_REQ;
rc = hvutil_transport_send(hvt, message, sizeof(*message));
if (rc) {
pr_debug("KVP: failed to communicate to the daemon: %d\n", rc);
if (cancel_delayed_work_sync(&kvp_work))
if (cancel_delayed_work_sync(&kvp_timeout_work)) {
kvp_respond_to_host(message, HV_E_FAIL);
kvp_transaction.state = HVUTIL_READY;
}
}
kfree(msg);
kfree(message);
return;
}
@ -478,17 +473,6 @@ kvp_respond_to_host(struct hv_kvp_msg *msg_to_host, int error)
u64 req_id;
int ret;
/*
* If a transaction is not active; log and return.
*/
if (!kvp_transaction.active) {
/*
* This is a spurious call!
*/
pr_warn("KVP: Transaction not active\n");
return;
}
/*
* Copy the global state for completing the transaction. Note that
* only one transaction can be active at a time.
@ -498,8 +482,6 @@ kvp_respond_to_host(struct hv_kvp_msg *msg_to_host, int error)
channel = kvp_transaction.recv_channel;
req_id = kvp_transaction.recv_req_id;
kvp_transaction.active = false;
icmsghdrp = (struct icmsg_hdr *)
&recv_buffer[sizeof(struct vmbuspipe_hdr)];
@ -586,7 +568,6 @@ response_done:
vmbus_sendpacket(channel, recv_buffer, buf_len, req_id,
VM_PKT_DATA_INBAND, 0);
poll_channel(channel);
}
/*
@ -612,7 +593,7 @@ void hv_kvp_onchannelcallback(void *context)
int util_fw_version;
int kvp_srv_version;
if (kvp_transaction.active) {
if (kvp_transaction.state > HVUTIL_READY) {
/*
* We will defer processing this callback once
* the current transaction is complete.
@ -620,6 +601,7 @@ void hv_kvp_onchannelcallback(void *context)
kvp_transaction.kvp_context = context;
return;
}
kvp_transaction.kvp_context = NULL;
vmbus_recvpacket(channel, recv_buffer, PAGE_SIZE * 4, &recvlen,
&requestid);
@ -664,9 +646,15 @@ void hv_kvp_onchannelcallback(void *context)
kvp_transaction.recv_len = recvlen;
kvp_transaction.recv_channel = channel;
kvp_transaction.recv_req_id = requestid;
kvp_transaction.active = true;
kvp_transaction.kvp_msg = kvp_msg;
if (kvp_transaction.state < HVUTIL_READY) {
/* Userspace is not registered yet */
kvp_respond_to_host(NULL, HV_E_FAIL);
return;
}
kvp_transaction.state = HVUTIL_HOSTMSG_RECEIVED;
/*
* Get the information from the
* user-mode component.
@ -677,7 +665,7 @@ void hv_kvp_onchannelcallback(void *context)
* user-mode not responding.
*/
schedule_work(&kvp_sendkey_work);
schedule_delayed_work(&kvp_work, 5*HZ);
schedule_delayed_work(&kvp_timeout_work, 5*HZ);
return;
@ -693,14 +681,16 @@ void hv_kvp_onchannelcallback(void *context)
}
static void kvp_on_reset(void)
{
if (cancel_delayed_work_sync(&kvp_timeout_work))
kvp_respond_to_host(NULL, HV_E_FAIL);
kvp_transaction.state = HVUTIL_DEVICE_INIT;
}
int
hv_kvp_init(struct hv_util_service *srv)
{
int err;
err = cn_add_callback(&kvp_id, kvp_name, kvp_cn_callback);
if (err)
return err;
recv_buffer = srv->recv_buffer;
/*
@ -709,14 +699,20 @@ hv_kvp_init(struct hv_util_service *srv)
* Defer processing channel callbacks until the daemon
* has registered.
*/
kvp_transaction.active = true;
kvp_transaction.state = HVUTIL_DEVICE_INIT;
hvt = hvutil_transport_init(kvp_devname, CN_KVP_IDX, CN_KVP_VAL,
kvp_on_msg, kvp_on_reset);
if (!hvt)
return -EFAULT;
return 0;
}
void hv_kvp_deinit(void)
{
cn_del_callback(&kvp_id);
cancel_delayed_work_sync(&kvp_work);
kvp_transaction.state = HVUTIL_DEVICE_DYING;
cancel_delayed_work_sync(&kvp_timeout_work);
cancel_work_sync(&kvp_sendkey_work);
hvutil_transport_destroy(hvt);
}

View File

@ -24,6 +24,9 @@
#include <linux/workqueue.h>
#include <linux/hyperv.h>
#include "hyperv_vmbus.h"
#include "hv_utils_transport.h"
#define VSS_MAJOR 5
#define VSS_MINOR 0
#define VSS_VERSION (VSS_MAJOR << 16 | VSS_MINOR)
@ -31,28 +34,39 @@
#define VSS_USERSPACE_TIMEOUT (msecs_to_jiffies(10 * 1000))
/*
* Global state maintained for transaction that is being processed.
* Note that only one transaction can be active at any point in time.
* Global state maintained for transaction that is being processed. For a class
* of integration services, including the "VSS service", the specified protocol
* is a "request/response" protocol which means that there can only be single
* outstanding transaction from the host at any given point in time. We use
* this to simplify memory management in this driver - we cache and process
* only one message at a time.
*
* This state is set when we receive a request from the host; we
* cleanup this state when the transaction is completed - when we respond
* to the host with the key value.
* While the request/response protocol is guaranteed by the host, we further
* ensure this by serializing packet processing in this driver - we do not
* read additional packets from the VMBUs until the current packet is fully
* handled.
*/
static struct {
bool active; /* transaction status - active or not */
int state; /* hvutil_device_state */
int recv_len; /* number of bytes received. */
struct vmbus_channel *recv_channel; /* chn we got the request */
u64 recv_req_id; /* request ID. */
struct hv_vss_msg *msg; /* current message */
void *vss_context; /* for the channel callback */
} vss_transaction;
static void vss_respond_to_host(int error);
static struct cb_id vss_id = { CN_VSS_IDX, CN_VSS_VAL };
static const char vss_name[] = "vss_kernel_module";
/*
* This state maintains the version number registered by the daemon.
*/
static int dm_reg_value;
static const char vss_devname[] = "vmbus/hv_vss";
static __u8 *recv_buffer;
static struct hvutil_transport *hvt;
static void vss_send_op(struct work_struct *dummy);
static void vss_timeout_func(struct work_struct *dummy);
@ -71,25 +85,69 @@ static void vss_timeout_func(struct work_struct *dummy)
*/
pr_warn("VSS: timeout waiting for daemon to reply\n");
vss_respond_to_host(HV_E_FAIL);
/* Transaction is finished, reset the state. */
if (vss_transaction.state > HVUTIL_READY)
vss_transaction.state = HVUTIL_READY;
hv_poll_channel(vss_transaction.vss_context,
hv_vss_onchannelcallback);
}
static void
vss_cn_callback(struct cn_msg *msg, struct netlink_skb_parms *nsp)
static int vss_handle_handshake(struct hv_vss_msg *vss_msg)
{
struct hv_vss_msg *vss_msg;
vss_msg = (struct hv_vss_msg *)msg->data;
if (vss_msg->vss_hdr.operation == VSS_OP_REGISTER) {
pr_info("VSS daemon registered\n");
vss_transaction.active = false;
if (vss_transaction.recv_channel != NULL)
hv_vss_onchannelcallback(vss_transaction.recv_channel);
return;
u32 our_ver = VSS_OP_REGISTER1;
switch (vss_msg->vss_hdr.operation) {
case VSS_OP_REGISTER:
/* Daemon doesn't expect us to reply */
dm_reg_value = VSS_OP_REGISTER;
break;
case VSS_OP_REGISTER1:
/* Daemon expects us to reply with our own version*/
if (hvutil_transport_send(hvt, &our_ver, sizeof(our_ver)))
return -EFAULT;
dm_reg_value = VSS_OP_REGISTER1;
break;
default:
return -EINVAL;
}
if (cancel_delayed_work_sync(&vss_timeout_work))
vss_respond_to_host(vss_msg->error);
vss_transaction.state = HVUTIL_READY;
pr_debug("VSS: userspace daemon ver. %d registered\n", dm_reg_value);
return 0;
}
static int vss_on_msg(void *msg, int len)
{
struct hv_vss_msg *vss_msg = (struct hv_vss_msg *)msg;
if (len != sizeof(*vss_msg))
return -EINVAL;
if (vss_msg->vss_hdr.operation == VSS_OP_REGISTER ||
vss_msg->vss_hdr.operation == VSS_OP_REGISTER1) {
/*
* Don't process registration messages if we're in the middle
* of a transaction processing.
*/
if (vss_transaction.state > HVUTIL_READY)
return -EINVAL;
return vss_handle_handshake(vss_msg);
} else if (vss_transaction.state == HVUTIL_USERSPACE_REQ) {
vss_transaction.state = HVUTIL_USERSPACE_RECV;
if (cancel_delayed_work_sync(&vss_timeout_work)) {
vss_respond_to_host(vss_msg->error);
/* Transaction is finished, reset the state. */
vss_transaction.state = HVUTIL_READY;
hv_poll_channel(vss_transaction.vss_context,
hv_vss_onchannelcallback);
}
} else {
/* This is a spurious call! */
pr_warn("VSS: Transaction not active\n");
return -EINVAL;
}
return 0;
}
@ -97,28 +155,29 @@ static void vss_send_op(struct work_struct *dummy)
{
int op = vss_transaction.msg->vss_hdr.operation;
int rc;
struct cn_msg *msg;
struct hv_vss_msg *vss_msg;
msg = kzalloc(sizeof(*msg) + sizeof(*vss_msg), GFP_ATOMIC);
if (!msg)
/* The transaction state is wrong. */
if (vss_transaction.state != HVUTIL_HOSTMSG_RECEIVED)
return;
vss_msg = (struct hv_vss_msg *)msg->data;
msg->id.idx = CN_VSS_IDX;
msg->id.val = CN_VSS_VAL;
vss_msg = kzalloc(sizeof(*vss_msg), GFP_KERNEL);
if (!vss_msg)
return;
vss_msg->vss_hdr.operation = op;
msg->len = sizeof(struct hv_vss_msg);
rc = cn_netlink_send(msg, 0, 0, GFP_ATOMIC);
vss_transaction.state = HVUTIL_USERSPACE_REQ;
rc = hvutil_transport_send(hvt, vss_msg, sizeof(*vss_msg));
if (rc) {
pr_warn("VSS: failed to communicate to the daemon: %d\n", rc);
if (cancel_delayed_work_sync(&vss_timeout_work))
if (cancel_delayed_work_sync(&vss_timeout_work)) {
vss_respond_to_host(HV_E_FAIL);
vss_transaction.state = HVUTIL_READY;
}
}
kfree(msg);
kfree(vss_msg);
return;
}
@ -135,17 +194,6 @@ vss_respond_to_host(int error)
struct vmbus_channel *channel;
u64 req_id;
/*
* If a transaction is not active; log and return.
*/
if (!vss_transaction.active) {
/*
* This is a spurious call!
*/
pr_warn("VSS: Transaction not active\n");
return;
}
/*
* Copy the global state for completing the transaction. Note that
* only one transaction can be active at a time.
@ -154,7 +202,6 @@ vss_respond_to_host(int error)
buf_len = vss_transaction.recv_len;
channel = vss_transaction.recv_channel;
req_id = vss_transaction.recv_req_id;
vss_transaction.active = false;
icmsghdrp = (struct icmsg_hdr *)
&recv_buffer[sizeof(struct vmbuspipe_hdr)];
@ -191,14 +238,15 @@ void hv_vss_onchannelcallback(void *context)
struct icmsg_hdr *icmsghdrp;
struct icmsg_negotiate *negop = NULL;
if (vss_transaction.active) {
if (vss_transaction.state > HVUTIL_READY) {
/*
* We will defer processing this callback once
* the current transaction is complete.
*/
vss_transaction.recv_channel = channel;
vss_transaction.vss_context = context;
return;
}
vss_transaction.vss_context = NULL;
vmbus_recvpacket(channel, recv_buffer, PAGE_SIZE * 2, &recvlen,
&requestid);
@ -224,7 +272,6 @@ void hv_vss_onchannelcallback(void *context)
vss_transaction.recv_len = recvlen;
vss_transaction.recv_channel = channel;
vss_transaction.recv_req_id = requestid;
vss_transaction.active = true;
vss_transaction.msg = (struct hv_vss_msg *)vss_msg;
switch (vss_msg->vss_hdr.operation) {
@ -241,6 +288,12 @@ void hv_vss_onchannelcallback(void *context)
*/
case VSS_OP_FREEZE:
case VSS_OP_THAW:
if (vss_transaction.state < HVUTIL_READY) {
/* Userspace is not registered yet */
vss_respond_to_host(HV_E_FAIL);
return;
}
vss_transaction.state = HVUTIL_HOSTMSG_RECEIVED;
schedule_work(&vss_send_op_work);
schedule_delayed_work(&vss_timeout_work,
VSS_USERSPACE_TIMEOUT);
@ -275,14 +328,16 @@ void hv_vss_onchannelcallback(void *context)
}
static void vss_on_reset(void)
{
if (cancel_delayed_work_sync(&vss_timeout_work))
vss_respond_to_host(HV_E_FAIL);
vss_transaction.state = HVUTIL_DEVICE_INIT;
}
int
hv_vss_init(struct hv_util_service *srv)
{
int err;
err = cn_add_callback(&vss_id, vss_name, vss_cn_callback);
if (err)
return err;
recv_buffer = srv->recv_buffer;
/*
@ -291,13 +346,20 @@ hv_vss_init(struct hv_util_service *srv)
* Defer processing channel callbacks until the daemon
* has registered.
*/
vss_transaction.active = true;
vss_transaction.state = HVUTIL_DEVICE_INIT;
hvt = hvutil_transport_init(vss_devname, CN_VSS_IDX, CN_VSS_VAL,
vss_on_msg, vss_on_reset);
if (!hvt)
return -EFAULT;
return 0;
}
void hv_vss_deinit(void)
{
cn_del_callback(&vss_id);
vss_transaction.state = HVUTIL_DEVICE_DYING;
cancel_delayed_work_sync(&vss_timeout_work);
cancel_work_sync(&vss_send_op_work);
hvutil_transport_destroy(hvt);
}

View File

@ -0,0 +1,276 @@
/*
* Kernel/userspace transport abstraction for Hyper-V util driver.
*
* Copyright (C) 2015, Vitaly Kuznetsov <vkuznets@redhat.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, GOOD TITLE or
* NON INFRINGEMENT. See the GNU General Public License for more
* details.
*
*/
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/poll.h>
#include "hyperv_vmbus.h"
#include "hv_utils_transport.h"
static DEFINE_SPINLOCK(hvt_list_lock);
static struct list_head hvt_list = LIST_HEAD_INIT(hvt_list);
static void hvt_reset(struct hvutil_transport *hvt)
{
mutex_lock(&hvt->outmsg_lock);
kfree(hvt->outmsg);
hvt->outmsg = NULL;
hvt->outmsg_len = 0;
mutex_unlock(&hvt->outmsg_lock);
if (hvt->on_reset)
hvt->on_reset();
}
static ssize_t hvt_op_read(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
struct hvutil_transport *hvt;
int ret;
hvt = container_of(file->f_op, struct hvutil_transport, fops);
if (wait_event_interruptible(hvt->outmsg_q, hvt->outmsg_len > 0))
return -EINTR;
mutex_lock(&hvt->outmsg_lock);
if (!hvt->outmsg) {
ret = -EAGAIN;
goto out_unlock;
}
if (count < hvt->outmsg_len) {
ret = -EINVAL;
goto out_unlock;
}
if (!copy_to_user(buf, hvt->outmsg, hvt->outmsg_len))
ret = hvt->outmsg_len;
else
ret = -EFAULT;
kfree(hvt->outmsg);
hvt->outmsg = NULL;
hvt->outmsg_len = 0;
out_unlock:
mutex_unlock(&hvt->outmsg_lock);
return ret;
}
static ssize_t hvt_op_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
struct hvutil_transport *hvt;
u8 *inmsg;
hvt = container_of(file->f_op, struct hvutil_transport, fops);
inmsg = kzalloc(count, GFP_KERNEL);
if (copy_from_user(inmsg, buf, count)) {
kfree(inmsg);
return -EFAULT;
}
if (hvt->on_msg(inmsg, count))
return -EFAULT;
kfree(inmsg);
return count;
}
static unsigned int hvt_op_poll(struct file *file, poll_table *wait)
{
struct hvutil_transport *hvt;
hvt = container_of(file->f_op, struct hvutil_transport, fops);
poll_wait(file, &hvt->outmsg_q, wait);
if (hvt->outmsg_len > 0)
return POLLIN | POLLRDNORM;
return 0;
}
static int hvt_op_open(struct inode *inode, struct file *file)
{
struct hvutil_transport *hvt;
hvt = container_of(file->f_op, struct hvutil_transport, fops);
/*
* Switching to CHARDEV mode. We switch bach to INIT when device
* gets released.
*/
if (hvt->mode == HVUTIL_TRANSPORT_INIT)
hvt->mode = HVUTIL_TRANSPORT_CHARDEV;
else if (hvt->mode == HVUTIL_TRANSPORT_NETLINK) {
/*
* We're switching from netlink communication to using char
* device. Issue the reset first.
*/
hvt_reset(hvt);
hvt->mode = HVUTIL_TRANSPORT_CHARDEV;
} else
return -EBUSY;
return 0;
}
static int hvt_op_release(struct inode *inode, struct file *file)
{
struct hvutil_transport *hvt;
hvt = container_of(file->f_op, struct hvutil_transport, fops);
hvt->mode = HVUTIL_TRANSPORT_INIT;
/*
* Cleanup message buffers to avoid spurious messages when the daemon
* connects back.
*/
hvt_reset(hvt);
return 0;
}
static void hvt_cn_callback(struct cn_msg *msg, struct netlink_skb_parms *nsp)
{
struct hvutil_transport *hvt, *hvt_found = NULL;
spin_lock(&hvt_list_lock);
list_for_each_entry(hvt, &hvt_list, list) {
if (hvt->cn_id.idx == msg->id.idx &&
hvt->cn_id.val == msg->id.val) {
hvt_found = hvt;
break;
}
}
spin_unlock(&hvt_list_lock);
if (!hvt_found) {
pr_warn("hvt_cn_callback: spurious message received!\n");
return;
}
/*
* Switching to NETLINK mode. Switching to CHARDEV happens when someone
* opens the device.
*/
if (hvt->mode == HVUTIL_TRANSPORT_INIT)
hvt->mode = HVUTIL_TRANSPORT_NETLINK;
if (hvt->mode == HVUTIL_TRANSPORT_NETLINK)
hvt_found->on_msg(msg->data, msg->len);
else
pr_warn("hvt_cn_callback: unexpected netlink message!\n");
}
int hvutil_transport_send(struct hvutil_transport *hvt, void *msg, int len)
{
struct cn_msg *cn_msg;
int ret = 0;
if (hvt->mode == HVUTIL_TRANSPORT_INIT) {
return -EINVAL;
} else if (hvt->mode == HVUTIL_TRANSPORT_NETLINK) {
cn_msg = kzalloc(sizeof(*cn_msg) + len, GFP_ATOMIC);
if (!msg)
return -ENOMEM;
cn_msg->id.idx = hvt->cn_id.idx;
cn_msg->id.val = hvt->cn_id.val;
cn_msg->len = len;
memcpy(cn_msg->data, msg, len);
ret = cn_netlink_send(cn_msg, 0, 0, GFP_ATOMIC);
kfree(cn_msg);
return ret;
}
/* HVUTIL_TRANSPORT_CHARDEV */
mutex_lock(&hvt->outmsg_lock);
if (hvt->outmsg) {
/* Previous message wasn't received */
ret = -EFAULT;
goto out_unlock;
}
hvt->outmsg = kzalloc(len, GFP_KERNEL);
memcpy(hvt->outmsg, msg, len);
hvt->outmsg_len = len;
wake_up_interruptible(&hvt->outmsg_q);
out_unlock:
mutex_unlock(&hvt->outmsg_lock);
return ret;
}
struct hvutil_transport *hvutil_transport_init(const char *name,
u32 cn_idx, u32 cn_val,
int (*on_msg)(void *, int),
void (*on_reset)(void))
{
struct hvutil_transport *hvt;
hvt = kzalloc(sizeof(*hvt), GFP_KERNEL);
if (!hvt)
return NULL;
hvt->cn_id.idx = cn_idx;
hvt->cn_id.val = cn_val;
hvt->mdev.minor = MISC_DYNAMIC_MINOR;
hvt->mdev.name = name;
hvt->fops.owner = THIS_MODULE;
hvt->fops.read = hvt_op_read;
hvt->fops.write = hvt_op_write;
hvt->fops.poll = hvt_op_poll;
hvt->fops.open = hvt_op_open;
hvt->fops.release = hvt_op_release;
hvt->mdev.fops = &hvt->fops;
init_waitqueue_head(&hvt->outmsg_q);
mutex_init(&hvt->outmsg_lock);
spin_lock(&hvt_list_lock);
list_add(&hvt->list, &hvt_list);
spin_unlock(&hvt_list_lock);
hvt->on_msg = on_msg;
hvt->on_reset = on_reset;
if (misc_register(&hvt->mdev))
goto err_free_hvt;
/* Use cn_id.idx/cn_id.val to determine if we need to setup netlink */
if (hvt->cn_id.idx > 0 && hvt->cn_id.val > 0 &&
cn_add_callback(&hvt->cn_id, name, hvt_cn_callback))
goto err_free_hvt;
return hvt;
err_free_hvt:
kfree(hvt);
return NULL;
}
void hvutil_transport_destroy(struct hvutil_transport *hvt)
{
spin_lock(&hvt_list_lock);
list_del(&hvt->list);
spin_unlock(&hvt_list_lock);
if (hvt->cn_id.idx > 0 && hvt->cn_id.val > 0)
cn_del_callback(&hvt->cn_id);
misc_deregister(&hvt->mdev);
kfree(hvt->outmsg);
kfree(hvt);
}

View File

@ -0,0 +1,51 @@
/*
* Kernel/userspace transport abstraction for Hyper-V util driver.
*
* Copyright (C) 2015, Vitaly Kuznetsov <vkuznets@redhat.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, GOOD TITLE or
* NON INFRINGEMENT. See the GNU General Public License for more
* details.
*
*/
#ifndef _HV_UTILS_TRANSPORT_H
#define _HV_UTILS_TRANSPORT_H
#include <linux/connector.h>
#include <linux/miscdevice.h>
enum hvutil_transport_mode {
HVUTIL_TRANSPORT_INIT = 0,
HVUTIL_TRANSPORT_NETLINK,
HVUTIL_TRANSPORT_CHARDEV,
};
struct hvutil_transport {
int mode; /* hvutil_transport_mode */
struct file_operations fops; /* file operations */
struct miscdevice mdev; /* misc device */
struct cb_id cn_id; /* CN_*_IDX/CN_*_VAL */
struct list_head list; /* hvt_list */
int (*on_msg)(void *, int); /* callback on new user message */
void (*on_reset)(void); /* callback when userspace drops */
u8 *outmsg; /* message to the userspace */
int outmsg_len; /* its length */
wait_queue_head_t outmsg_q; /* poll/read wait queue */
struct mutex outmsg_lock; /* protects outmsg */
};
struct hvutil_transport *hvutil_transport_init(const char *name,
u32 cn_idx, u32 cn_val,
int (*on_msg)(void *, int),
void (*on_reset)(void));
int hvutil_transport_send(struct hvutil_transport *hvt, void *msg, int len);
void hvutil_transport_destroy(struct hvutil_transport *hvt);
#endif /* _HV_UTILS_TRANSPORT_H */

View File

@ -647,6 +647,7 @@ struct vmbus_connection {
atomic_t next_gpadl_handle;
struct completion unload_event;
/*
* Represents channel interrupts. Each bit position represents a
* channel. When a channel sends an interrupt via VMBUS, it finds its
@ -730,9 +731,39 @@ int vmbus_set_event(struct vmbus_channel *channel);
void vmbus_on_event(unsigned long data);
int hv_kvp_init(struct hv_util_service *);
void hv_kvp_deinit(void);
void hv_kvp_onchannelcallback(void *);
int hv_vss_init(struct hv_util_service *);
void hv_vss_deinit(void);
void hv_vss_onchannelcallback(void *);
int hv_fcopy_init(struct hv_util_service *);
void hv_fcopy_deinit(void);
void hv_fcopy_onchannelcallback(void *);
void vmbus_initiate_unload(void);
static inline void hv_poll_channel(struct vmbus_channel *channel,
void (*cb)(void *))
{
if (!channel)
return;
if (channel->target_cpu != smp_processor_id())
smp_call_function_single(channel->target_cpu,
cb, channel, true);
else
cb(channel);
}
enum hvutil_device_state {
HVUTIL_DEVICE_INIT = 0, /* driver is loaded, waiting for userspace */
HVUTIL_READY, /* userspace is registered */
HVUTIL_HOSTMSG_RECEIVED, /* message from the host was received */
HVUTIL_USERSPACE_REQ, /* request to userspace was sent */
HVUTIL_USERSPACE_RECV, /* reply from userspace was received */
HVUTIL_DEVICE_DYING, /* driver unload is in progress */
};
#endif /* _HYPERV_VMBUS_H */

View File

@ -1035,6 +1035,15 @@ acpi_walk_err:
return ret_val;
}
static int vmbus_acpi_remove(struct acpi_device *device)
{
int ret = 0;
if (hyperv_mmio.start && hyperv_mmio.end)
ret = release_resource(&hyperv_mmio);
return ret;
}
static const struct acpi_device_id vmbus_acpi_device_ids[] = {
{"VMBUS", 0},
{"VMBus", 0},
@ -1047,6 +1056,7 @@ static struct acpi_driver vmbus_acpi_driver = {
.ids = vmbus_acpi_device_ids,
.ops = {
.add = vmbus_acpi_add,
.remove = vmbus_acpi_remove,
},
};
@ -1096,15 +1106,22 @@ static void __exit vmbus_exit(void)
vmbus_connection.conn_state = DISCONNECTED;
hv_synic_clockevents_cleanup();
vmbus_disconnect();
hv_remove_vmbus_irq();
tasklet_kill(&msg_dpc);
vmbus_free_channels();
if (ms_hyperv.features & HV_FEATURE_GUEST_CRASH_MSR_AVAILABLE) {
atomic_notifier_chain_unregister(&panic_notifier_list,
&hyperv_panic_block);
}
bus_unregister(&hv_bus);
hv_cleanup();
for_each_online_cpu(cpu)
for_each_online_cpu(cpu) {
tasklet_kill(hv_context.event_dpc[cpu]);
smp_call_function_single(cpu, hv_synic_cleanup, NULL, 1);
}
acpi_bus_unregister_driver(&vmbus_acpi_driver);
hv_cpu_hotplug_quirk(false);
vmbus_disconnect();
}

View File

@ -371,6 +371,17 @@ config SENSORS_DS1621
This driver can also be built as a module. If so, the module
will be called ds1621.
config SENSORS_DELL_SMM
tristate "Dell laptop SMM BIOS hwmon driver"
depends on X86
help
This hwmon driver adds support for reporting temperature of different
sensors and controls the fans on Dell laptops via System Management
Mode provided by Dell BIOS.
When option I8K is also enabled this driver provides legacy /proc/i8k
userspace interface for i8kutils package.
config SENSORS_DA9052_ADC
tristate "Dialog DA9052/DA9053 ADC"
depends on PMIC_DA9052

View File

@ -49,6 +49,7 @@ obj-$(CONFIG_SENSORS_ATXP1) += atxp1.o
obj-$(CONFIG_SENSORS_CORETEMP) += coretemp.o
obj-$(CONFIG_SENSORS_DA9052_ADC)+= da9052-hwmon.o
obj-$(CONFIG_SENSORS_DA9055)+= da9055-hwmon.o
obj-$(CONFIG_SENSORS_DELL_SMM) += dell-smm-hwmon.o
obj-$(CONFIG_SENSORS_DME1737) += dme1737.o
obj-$(CONFIG_SENSORS_DS620) += ds620.o
obj-$(CONFIG_SENSORS_DS1621) += ds1621.o

View File

@ -1,12 +1,12 @@
/*
* i8k.c -- Linux driver for accessing the SMM BIOS on Dell laptops.
* dell-smm-hwmon.c -- Linux driver for accessing the SMM BIOS on Dell laptops.
*
* Copyright (C) 2001 Massimo Dal Zotto <dz@debian.org>
*
* Hwmon integration:
* Copyright (C) 2011 Jean Delvare <jdelvare@suse.de>
* Copyright (C) 2013, 2014 Guenter Roeck <linux@roeck-us.net>
* Copyright (C) 2014 Pali Rohár <pali.rohar@gmail.com>
* Copyright (C) 2014, 2015 Pali Rohár <pali.rohar@gmail.com>
*
* 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
@ -80,8 +80,10 @@ static uint i8k_fan_max = I8K_FAN_HIGH;
#define I8K_HWMON_HAVE_FAN2 (1 << 5)
MODULE_AUTHOR("Massimo Dal Zotto (dz@debian.org)");
MODULE_DESCRIPTION("Driver for accessing SMM BIOS on Dell laptops");
MODULE_AUTHOR("Pali Rohár <pali.rohar@gmail.com>");
MODULE_DESCRIPTION("Dell laptop SMM BIOS hwmon driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("i8k");
static bool force;
module_param(force, bool, 0);
@ -91,6 +93,7 @@ static bool ignore_dmi;
module_param(ignore_dmi, bool, 0);
MODULE_PARM_DESC(ignore_dmi, "Continue probing hardware even if DMI data does not match");
#if IS_ENABLED(CONFIG_I8K)
static bool restricted;
module_param(restricted, bool, 0);
MODULE_PARM_DESC(restricted, "Allow fan control if SYS_ADMIN capability set");
@ -98,6 +101,7 @@ MODULE_PARM_DESC(restricted, "Allow fan control if SYS_ADMIN capability set");
static bool power_status;
module_param(power_status, bool, 0600);
MODULE_PARM_DESC(power_status, "Report power status in /proc/i8k");
#endif
static uint fan_mult;
module_param(fan_mult, uint, 0);
@ -107,18 +111,6 @@ static uint fan_max;
module_param(fan_max, uint, 0);
MODULE_PARM_DESC(fan_max, "Maximum configurable fan speed (default: autodetect)");
static int i8k_open_fs(struct inode *inode, struct file *file);
static long i8k_ioctl(struct file *, unsigned int, unsigned long);
static const struct file_operations i8k_fops = {
.owner = THIS_MODULE,
.open = i8k_open_fs,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
.unlocked_ioctl = i8k_ioctl,
};
struct smm_regs {
unsigned int eax;
unsigned int ebx __packed;
@ -218,45 +210,6 @@ out:
return rc;
}
/*
* Read the Fn key status.
*/
static int i8k_get_fn_status(void)
{
struct smm_regs regs = { .eax = I8K_SMM_FN_STATUS, };
int rc;
rc = i8k_smm(&regs);
if (rc < 0)
return rc;
switch ((regs.eax >> I8K_FN_SHIFT) & I8K_FN_MASK) {
case I8K_FN_UP:
return I8K_VOL_UP;
case I8K_FN_DOWN:
return I8K_VOL_DOWN;
case I8K_FN_MUTE:
return I8K_VOL_MUTE;
default:
return 0;
}
}
/*
* Read the power status.
*/
static int i8k_get_power_status(void)
{
struct smm_regs regs = { .eax = I8K_SMM_POWER_STATUS, };
int rc;
rc = i8k_smm(&regs);
if (rc < 0)
return rc;
return (regs.eax & 0xff) == I8K_POWER_AC ? I8K_AC : I8K_BATTERY;
}
/*
* Read the fan status.
*/
@ -376,6 +329,51 @@ static int i8k_get_dell_signature(int req_fn)
return regs.eax == 1145651527 && regs.edx == 1145392204 ? 0 : -1;
}
#if IS_ENABLED(CONFIG_I8K)
/*
* Read the Fn key status.
*/
static int i8k_get_fn_status(void)
{
struct smm_regs regs = { .eax = I8K_SMM_FN_STATUS, };
int rc;
rc = i8k_smm(&regs);
if (rc < 0)
return rc;
switch ((regs.eax >> I8K_FN_SHIFT) & I8K_FN_MASK) {
case I8K_FN_UP:
return I8K_VOL_UP;
case I8K_FN_DOWN:
return I8K_VOL_DOWN;
case I8K_FN_MUTE:
return I8K_VOL_MUTE;
default:
return 0;
}
}
/*
* Read the power status.
*/
static int i8k_get_power_status(void)
{
struct smm_regs regs = { .eax = I8K_SMM_POWER_STATUS, };
int rc;
rc = i8k_smm(&regs);
if (rc < 0)
return rc;
return (regs.eax & 0xff) == I8K_POWER_AC ? I8K_AC : I8K_BATTERY;
}
/*
* Procfs interface
*/
static int
i8k_ioctl_unlocked(struct file *fp, unsigned int cmd, unsigned long arg)
{
@ -526,6 +524,37 @@ static int i8k_open_fs(struct inode *inode, struct file *file)
return single_open(file, i8k_proc_show, NULL);
}
static const struct file_operations i8k_fops = {
.owner = THIS_MODULE,
.open = i8k_open_fs,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
.unlocked_ioctl = i8k_ioctl,
};
static void __init i8k_init_procfs(void)
{
/* Register the proc entry */
proc_create("i8k", 0, NULL, &i8k_fops);
}
static void __exit i8k_exit_procfs(void)
{
remove_proc_entry("i8k", NULL);
}
#else
static inline void __init i8k_init_procfs(void)
{
}
static inline void __exit i8k_exit_procfs(void)
{
}
#endif
/*
* Hwmon interface
@ -748,8 +777,8 @@ static int __init i8k_init_hwmon(void)
if (err >= 0)
i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN2;
i8k_hwmon_dev = hwmon_device_register_with_groups(NULL, "i8k", NULL,
i8k_groups);
i8k_hwmon_dev = hwmon_device_register_with_groups(NULL, "dell-smm",
NULL, i8k_groups);
if (IS_ERR(i8k_hwmon_dev)) {
err = PTR_ERR(i8k_hwmon_dev);
i8k_hwmon_dev = NULL;
@ -974,33 +1003,24 @@ static int __init i8k_probe(void)
static int __init i8k_init(void)
{
struct proc_dir_entry *proc_i8k;
int err;
/* Are we running on an supported laptop? */
if (i8k_probe())
return -ENODEV;
/* Register the proc entry */
proc_i8k = proc_create("i8k", 0, NULL, &i8k_fops);
if (!proc_i8k)
return -ENOENT;
err = i8k_init_hwmon();
if (err)
goto exit_remove_proc;
return err;
i8k_init_procfs();
return 0;
exit_remove_proc:
remove_proc_entry("i8k", NULL);
return err;
}
static void __exit i8k_exit(void)
{
hwmon_device_unregister(i8k_hwmon_dev);
remove_proc_entry("i8k", NULL);
i8k_exit_procfs();
}
module_init(i8k_init);

View File

@ -58,4 +58,23 @@ config CORESIGHT_SOURCE_ETM3X
which allows tracing the instructions that a processor is executing
This is primarily useful for instruction level tracing. Depending
the ETM version data tracing may also be available.
config CORESIGHT_SOURCE_ETM4X
bool "CoreSight Embedded Trace Macrocell 4.x driver"
depends on ARM64
select CORESIGHT_LINKS_AND_SINKS
help
This driver provides support for the ETM4.x tracer module, tracing the
instructions that a processor is executing. This is primarily useful
for instruction level tracing. Depending on the implemented version
data tracing may also be available.
config CORESIGHT_QCOM_REPLICATOR
bool "Qualcomm CoreSight Replicator driver"
depends on CORESIGHT_LINKS_AND_SINKS
help
This enables support for Qualcomm CoreSight link driver. The
programmable ATB replicator sends the ATB trace stream from the
ETB/ETF to the TPIUi and ETR.
endif

View File

@ -9,3 +9,5 @@ obj-$(CONFIG_CORESIGHT_SINK_ETBV10) += coresight-etb10.o
obj-$(CONFIG_CORESIGHT_LINKS_AND_SINKS) += coresight-funnel.o \
coresight-replicator.o
obj-$(CONFIG_CORESIGHT_SOURCE_ETM3X) += coresight-etm3x.o coresight-etm-cp14.o
obj-$(CONFIG_CORESIGHT_SOURCE_ETM4X) += coresight-etm4x.o
obj-$(CONFIG_CORESIGHT_QCOM_REPLICATOR) += coresight-replicator-qcom.o

View File

@ -22,10 +22,11 @@
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/clk.h>
#include <linux/pm_runtime.h>
#include <linux/seq_file.h>
#include <linux/coresight.h>
#include <linux/amba/bus.h>
#include <linux/clk.h>
#include "coresight-priv.h"
@ -66,9 +67,9 @@
* struct etb_drvdata - specifics associated to an ETB component
* @base: memory mapped base address for this component.
* @dev: the device entity associated to this component.
* @atclk: optional clock for the core parts of the ETB.
* @csdev: component vitals needed by the framework.
* @miscdev: specifics to handle "/dev/xyz.etb" entry.
* @clk: the clock this component is associated to.
* @spinlock: only one at a time pls.
* @in_use: synchronise user space access to etb buffer.
* @buf: area of memory where ETB buffer content gets sent.
@ -79,9 +80,9 @@
struct etb_drvdata {
void __iomem *base;
struct device *dev;
struct clk *atclk;
struct coresight_device *csdev;
struct miscdevice miscdev;
struct clk *clk;
spinlock_t spinlock;
atomic_t in_use;
u8 *buf;
@ -92,17 +93,14 @@ struct etb_drvdata {
static unsigned int etb_get_buffer_depth(struct etb_drvdata *drvdata)
{
int ret;
u32 depth = 0;
ret = clk_prepare_enable(drvdata->clk);
if (ret)
return ret;
pm_runtime_get_sync(drvdata->dev);
/* RO registers don't need locking */
depth = readl_relaxed(drvdata->base + ETB_RAM_DEPTH_REG);
clk_disable_unprepare(drvdata->clk);
pm_runtime_put(drvdata->dev);
return depth;
}
@ -137,12 +135,9 @@ static void etb_enable_hw(struct etb_drvdata *drvdata)
static int etb_enable(struct coresight_device *csdev)
{
struct etb_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
int ret;
unsigned long flags;
ret = clk_prepare_enable(drvdata->clk);
if (ret)
return ret;
pm_runtime_get_sync(drvdata->dev);
spin_lock_irqsave(&drvdata->spinlock, flags);
etb_enable_hw(drvdata);
@ -252,7 +247,7 @@ static void etb_disable(struct coresight_device *csdev)
drvdata->enable = false;
spin_unlock_irqrestore(&drvdata->spinlock, flags);
clk_disable_unprepare(drvdata->clk);
pm_runtime_put(drvdata->dev);
dev_info(drvdata->dev, "ETB disabled\n");
}
@ -339,16 +334,12 @@ static const struct file_operations etb_fops = {
static ssize_t status_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
int ret;
unsigned long flags;
u32 etb_rdr, etb_sr, etb_rrp, etb_rwp;
u32 etb_trg, etb_cr, etb_ffsr, etb_ffcr;
struct etb_drvdata *drvdata = dev_get_drvdata(dev->parent);
ret = clk_prepare_enable(drvdata->clk);
if (ret)
goto out;
pm_runtime_get_sync(drvdata->dev);
spin_lock_irqsave(&drvdata->spinlock, flags);
CS_UNLOCK(drvdata->base);
@ -364,7 +355,7 @@ static ssize_t status_show(struct device *dev,
CS_LOCK(drvdata->base);
spin_unlock_irqrestore(&drvdata->spinlock, flags);
clk_disable_unprepare(drvdata->clk);
pm_runtime_put(drvdata->dev);
return sprintf(buf,
"Depth:\t\t0x%x\n"
@ -377,7 +368,7 @@ static ssize_t status_show(struct device *dev,
"Flush ctrl:\t0x%x\n",
etb_rdr, etb_sr, etb_rrp, etb_rwp,
etb_trg, etb_cr, etb_ffsr, etb_ffcr);
out:
return -EINVAL;
}
static DEVICE_ATTR_RO(status);
@ -438,6 +429,12 @@ static int etb_probe(struct amba_device *adev, const struct amba_id *id)
return -ENOMEM;
drvdata->dev = &adev->dev;
drvdata->atclk = devm_clk_get(&adev->dev, "atclk"); /* optional */
if (!IS_ERR(drvdata->atclk)) {
ret = clk_prepare_enable(drvdata->atclk);
if (ret)
return ret;
}
dev_set_drvdata(dev, drvdata);
/* validity for the resource is already checked by the AMBA core */
@ -449,21 +446,19 @@ static int etb_probe(struct amba_device *adev, const struct amba_id *id)
spin_lock_init(&drvdata->spinlock);
drvdata->clk = adev->pclk;
ret = clk_prepare_enable(drvdata->clk);
if (ret)
return ret;
drvdata->buffer_depth = etb_get_buffer_depth(drvdata);
clk_disable_unprepare(drvdata->clk);
pm_runtime_put(&adev->dev);
if (drvdata->buffer_depth < 0)
if (drvdata->buffer_depth & 0x80000000)
return -EINVAL;
drvdata->buf = devm_kzalloc(dev,
drvdata->buffer_depth * 4, GFP_KERNEL);
if (!drvdata->buf)
if (!drvdata->buf) {
dev_err(dev, "Failed to allocate %u bytes for buffer data\n",
drvdata->buffer_depth * 4);
return -ENOMEM;
}
desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL);
if (!desc)
@ -503,6 +498,32 @@ static int etb_remove(struct amba_device *adev)
return 0;
}
#ifdef CONFIG_PM
static int etb_runtime_suspend(struct device *dev)
{
struct etb_drvdata *drvdata = dev_get_drvdata(dev);
if (drvdata && !IS_ERR(drvdata->atclk))
clk_disable_unprepare(drvdata->atclk);
return 0;
}
static int etb_runtime_resume(struct device *dev)
{
struct etb_drvdata *drvdata = dev_get_drvdata(dev);
if (drvdata && !IS_ERR(drvdata->atclk))
clk_prepare_enable(drvdata->atclk);
return 0;
}
#endif
static const struct dev_pm_ops etb_dev_pm_ops = {
SET_RUNTIME_PM_OPS(etb_runtime_suspend, etb_runtime_resume, NULL)
};
static struct amba_id etb_ids[] = {
{
.id = 0x0003b907,
@ -515,6 +536,8 @@ static struct amba_driver etb_driver = {
.drv = {
.name = "coresight-etb10",
.owner = THIS_MODULE,
.pm = &etb_dev_pm_ops,
},
.probe = etb_probe,
.remove = etb_remove,

View File

@ -140,8 +140,8 @@
* struct etm_drvdata - specifics associated to an ETM component
* @base: memory mapped base address for this component.
* @dev: the device entity associated to this component.
* @atclk: optional clock for the core parts of the ETM.
* @csdev: component vitals needed by the framework.
* @clk: the clock this component is associated to.
* @spinlock: only one at a time pls.
* @cpu: the cpu this component is affined to.
* @port_size: port size as reported by ETMCR bit 4-6 and 21.
@ -192,8 +192,8 @@
struct etm_drvdata {
void __iomem *base;
struct device *dev;
struct clk *atclk;
struct coresight_device *csdev;
struct clk *clk;
spinlock_t spinlock;
int cpu;
int port_size;

View File

@ -23,13 +23,14 @@
#include <linux/smp.h>
#include <linux/sysfs.h>
#include <linux/stat.h>
#include <linux/clk.h>
#include <linux/pm_runtime.h>
#include <linux/cpu.h>
#include <linux/of.h>
#include <linux/coresight.h>
#include <linux/amba/bus.h>
#include <linux/seq_file.h>
#include <linux/uaccess.h>
#include <linux/clk.h>
#include <asm/sections.h>
#include "coresight-etm.h"
@ -325,9 +326,7 @@ static int etm_trace_id(struct coresight_device *csdev)
if (!drvdata->enable)
return drvdata->traceid;
if (clk_prepare_enable(drvdata->clk))
goto out;
pm_runtime_get_sync(csdev->dev.parent);
spin_lock_irqsave(&drvdata->spinlock, flags);
@ -336,8 +335,8 @@ static int etm_trace_id(struct coresight_device *csdev)
CS_LOCK(drvdata->base);
spin_unlock_irqrestore(&drvdata->spinlock, flags);
clk_disable_unprepare(drvdata->clk);
out:
pm_runtime_put(csdev->dev.parent);
return trace_id;
}
@ -346,10 +345,7 @@ static int etm_enable(struct coresight_device *csdev)
struct etm_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
int ret;
ret = clk_prepare_enable(drvdata->clk);
if (ret)
goto err_clk;
pm_runtime_get_sync(csdev->dev.parent);
spin_lock(&drvdata->spinlock);
/*
@ -373,8 +369,7 @@ static int etm_enable(struct coresight_device *csdev)
return 0;
err:
spin_unlock(&drvdata->spinlock);
clk_disable_unprepare(drvdata->clk);
err_clk:
pm_runtime_put(csdev->dev.parent);
return ret;
}
@ -423,8 +418,7 @@ static void etm_disable(struct coresight_device *csdev)
spin_unlock(&drvdata->spinlock);
put_online_cpus();
clk_disable_unprepare(drvdata->clk);
pm_runtime_put(csdev->dev.parent);
dev_info(drvdata->dev, "ETM tracing disabled\n");
}
@ -474,14 +468,10 @@ static DEVICE_ATTR_RO(nr_ctxid_cmp);
static ssize_t etmsr_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
int ret;
unsigned long flags, val;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
ret = clk_prepare_enable(drvdata->clk);
if (ret)
return ret;
pm_runtime_get_sync(drvdata->dev);
spin_lock_irqsave(&drvdata->spinlock, flags);
CS_UNLOCK(drvdata->base);
@ -489,7 +479,7 @@ static ssize_t etmsr_show(struct device *dev,
CS_LOCK(drvdata->base);
spin_unlock_irqrestore(&drvdata->spinlock, flags);
clk_disable_unprepare(drvdata->clk);
pm_runtime_put(drvdata->dev);
return sprintf(buf, "%#lx\n", val);
}
@ -1317,7 +1307,6 @@ static DEVICE_ATTR_RW(seq_13_event);
static ssize_t seq_curr_state_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
int ret;
unsigned long val, flags;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
@ -1326,10 +1315,7 @@ static ssize_t seq_curr_state_show(struct device *dev,
goto out;
}
ret = clk_prepare_enable(drvdata->clk);
if (ret)
return ret;
pm_runtime_get_sync(drvdata->dev);
spin_lock_irqsave(&drvdata->spinlock, flags);
CS_UNLOCK(drvdata->base);
@ -1337,7 +1323,7 @@ static ssize_t seq_curr_state_show(struct device *dev,
CS_LOCK(drvdata->base);
spin_unlock_irqrestore(&drvdata->spinlock, flags);
clk_disable_unprepare(drvdata->clk);
pm_runtime_put(drvdata->dev);
out:
return sprintf(buf, "%#lx\n", val);
}
@ -1521,10 +1507,7 @@ static ssize_t status_show(struct device *dev,
unsigned long flags;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
ret = clk_prepare_enable(drvdata->clk);
if (ret)
return ret;
pm_runtime_get_sync(drvdata->dev);
spin_lock_irqsave(&drvdata->spinlock, flags);
CS_UNLOCK(drvdata->base);
@ -1550,7 +1533,7 @@ static ssize_t status_show(struct device *dev,
CS_LOCK(drvdata->base);
spin_unlock_irqrestore(&drvdata->spinlock, flags);
clk_disable_unprepare(drvdata->clk);
pm_runtime_put(drvdata->dev);
return ret;
}
@ -1559,7 +1542,6 @@ static DEVICE_ATTR_RO(status);
static ssize_t traceid_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
int ret;
unsigned long val, flags;
struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent);
@ -1568,10 +1550,7 @@ static ssize_t traceid_show(struct device *dev,
goto out;
}
ret = clk_prepare_enable(drvdata->clk);
if (ret)
return ret;
pm_runtime_get_sync(drvdata->dev);
spin_lock_irqsave(&drvdata->spinlock, flags);
CS_UNLOCK(drvdata->base);
@ -1579,7 +1558,7 @@ static ssize_t traceid_show(struct device *dev,
CS_LOCK(drvdata->base);
spin_unlock_irqrestore(&drvdata->spinlock, flags);
clk_disable_unprepare(drvdata->clk);
pm_runtime_put(drvdata->dev);
out:
return sprintf(buf, "%#lx\n", val);
}
@ -1817,10 +1796,12 @@ static int etm_probe(struct amba_device *adev, const struct amba_id *id)
spin_lock_init(&drvdata->spinlock);
drvdata->clk = adev->pclk;
ret = clk_prepare_enable(drvdata->clk);
if (ret)
return ret;
drvdata->atclk = devm_clk_get(&adev->dev, "atclk"); /* optional */
if (!IS_ERR(drvdata->atclk)) {
ret = clk_prepare_enable(drvdata->atclk);
if (ret)
return ret;
}
drvdata->cpu = pdata ? pdata->cpu : 0;
@ -1845,8 +1826,6 @@ static int etm_probe(struct amba_device *adev, const struct amba_id *id)
}
etm_init_default_data(drvdata);
clk_disable_unprepare(drvdata->clk);
desc->type = CORESIGHT_DEV_TYPE_SOURCE;
desc->subtype.source_subtype = CORESIGHT_DEV_SUBTYPE_SOURCE_PROC;
desc->ops = &etm_cs_ops;
@ -1859,7 +1838,8 @@ static int etm_probe(struct amba_device *adev, const struct amba_id *id)
goto err_arch_supported;
}
dev_info(dev, "ETM initialized\n");
pm_runtime_put(&adev->dev);
dev_info(dev, "%s initialized\n", (char *)id->data);
if (boot_enable) {
coresight_enable(drvdata->csdev);
@ -1869,7 +1849,6 @@ static int etm_probe(struct amba_device *adev, const struct amba_id *id)
return 0;
err_arch_supported:
clk_disable_unprepare(drvdata->clk);
if (--etm_count == 0)
unregister_hotcpu_notifier(&etm_cpu_notifier);
return ret;
@ -1886,22 +1865,52 @@ static int etm_remove(struct amba_device *adev)
return 0;
}
#ifdef CONFIG_PM
static int etm_runtime_suspend(struct device *dev)
{
struct etm_drvdata *drvdata = dev_get_drvdata(dev);
if (drvdata && !IS_ERR(drvdata->atclk))
clk_disable_unprepare(drvdata->atclk);
return 0;
}
static int etm_runtime_resume(struct device *dev)
{
struct etm_drvdata *drvdata = dev_get_drvdata(dev);
if (drvdata && !IS_ERR(drvdata->atclk))
clk_prepare_enable(drvdata->atclk);
return 0;
}
#endif
static const struct dev_pm_ops etm_dev_pm_ops = {
SET_RUNTIME_PM_OPS(etm_runtime_suspend, etm_runtime_resume, NULL)
};
static struct amba_id etm_ids[] = {
{ /* ETM 3.3 */
.id = 0x0003b921,
.mask = 0x0003ffff,
.data = "ETM 3.3",
},
{ /* ETM 3.5 */
.id = 0x0003b956,
.mask = 0x0003ffff,
.data = "ETM 3.5",
},
{ /* PTM 1.0 */
.id = 0x0003b950,
.mask = 0x0003ffff,
.data = "PTM 1.0",
},
{ /* PTM 1.1 */
.id = 0x0003b95f,
.mask = 0x0003ffff,
.data = "PTM 1.1",
},
{ 0, 0},
};
@ -1910,23 +1919,14 @@ static struct amba_driver etm_driver = {
.drv = {
.name = "coresight-etm3x",
.owner = THIS_MODULE,
.pm = &etm_dev_pm_ops,
},
.probe = etm_probe,
.remove = etm_remove,
.id_table = etm_ids,
};
int __init etm_init(void)
{
return amba_driver_register(&etm_driver);
}
module_init(etm_init);
void __exit etm_exit(void)
{
amba_driver_unregister(&etm_driver);
}
module_exit(etm_exit);
module_amba_driver(etm_driver);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("CoreSight Program Flow Trace driver");

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,391 @@
/* Copyright (c) 2014-2015, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#ifndef _CORESIGHT_CORESIGHT_ETM_H
#define _CORESIGHT_CORESIGHT_ETM_H
#include <linux/spinlock.h>
#include "coresight-priv.h"
/*
* Device registers:
* 0x000 - 0x2FC: Trace registers
* 0x300 - 0x314: Management registers
* 0x318 - 0xEFC: Trace registers
* 0xF00: Management registers
* 0xFA0 - 0xFA4: Trace registers
* 0xFA8 - 0xFFC: Management registers
*/
/* Trace registers (0x000-0x2FC) */
/* Main control and configuration registers */
#define TRCPRGCTLR 0x004
#define TRCPROCSELR 0x008
#define TRCSTATR 0x00C
#define TRCCONFIGR 0x010
#define TRCAUXCTLR 0x018
#define TRCEVENTCTL0R 0x020
#define TRCEVENTCTL1R 0x024
#define TRCSTALLCTLR 0x02C
#define TRCTSCTLR 0x030
#define TRCSYNCPR 0x034
#define TRCCCCTLR 0x038
#define TRCBBCTLR 0x03C
#define TRCTRACEIDR 0x040
#define TRCQCTLR 0x044
/* Filtering control registers */
#define TRCVICTLR 0x080
#define TRCVIIECTLR 0x084
#define TRCVISSCTLR 0x088
#define TRCVIPCSSCTLR 0x08C
#define TRCVDCTLR 0x0A0
#define TRCVDSACCTLR 0x0A4
#define TRCVDARCCTLR 0x0A8
/* Derived resources registers */
#define TRCSEQEVRn(n) (0x100 + (n * 4))
#define TRCSEQRSTEVR 0x118
#define TRCSEQSTR 0x11C
#define TRCEXTINSELR 0x120
#define TRCCNTRLDVRn(n) (0x140 + (n * 4))
#define TRCCNTCTLRn(n) (0x150 + (n * 4))
#define TRCCNTVRn(n) (0x160 + (n * 4))
/* ID registers */
#define TRCIDR8 0x180
#define TRCIDR9 0x184
#define TRCIDR10 0x188
#define TRCIDR11 0x18C
#define TRCIDR12 0x190
#define TRCIDR13 0x194
#define TRCIMSPEC0 0x1C0
#define TRCIMSPECn(n) (0x1C0 + (n * 4))
#define TRCIDR0 0x1E0
#define TRCIDR1 0x1E4
#define TRCIDR2 0x1E8
#define TRCIDR3 0x1EC
#define TRCIDR4 0x1F0
#define TRCIDR5 0x1F4
#define TRCIDR6 0x1F8
#define TRCIDR7 0x1FC
/* Resource selection registers */
#define TRCRSCTLRn(n) (0x200 + (n * 4))
/* Single-shot comparator registers */
#define TRCSSCCRn(n) (0x280 + (n * 4))
#define TRCSSCSRn(n) (0x2A0 + (n * 4))
#define TRCSSPCICRn(n) (0x2C0 + (n * 4))
/* Management registers (0x300-0x314) */
#define TRCOSLAR 0x300
#define TRCOSLSR 0x304
#define TRCPDCR 0x310
#define TRCPDSR 0x314
/* Trace registers (0x318-0xEFC) */
/* Comparator registers */
#define TRCACVRn(n) (0x400 + (n * 8))
#define TRCACATRn(n) (0x480 + (n * 8))
#define TRCDVCVRn(n) (0x500 + (n * 16))
#define TRCDVCMRn(n) (0x580 + (n * 16))
#define TRCCIDCVRn(n) (0x600 + (n * 8))
#define TRCVMIDCVRn(n) (0x640 + (n * 8))
#define TRCCIDCCTLR0 0x680
#define TRCCIDCCTLR1 0x684
#define TRCVMIDCCTLR0 0x688
#define TRCVMIDCCTLR1 0x68C
/* Management register (0xF00) */
/* Integration control registers */
#define TRCITCTRL 0xF00
/* Trace registers (0xFA0-0xFA4) */
/* Claim tag registers */
#define TRCCLAIMSET 0xFA0
#define TRCCLAIMCLR 0xFA4
/* Management registers (0xFA8-0xFFC) */
#define TRCDEVAFF0 0xFA8
#define TRCDEVAFF1 0xFAC
#define TRCLAR 0xFB0
#define TRCLSR 0xFB4
#define TRCAUTHSTATUS 0xFB8
#define TRCDEVARCH 0xFBC
#define TRCDEVID 0xFC8
#define TRCDEVTYPE 0xFCC
#define TRCPIDR4 0xFD0
#define TRCPIDR5 0xFD4
#define TRCPIDR6 0xFD8
#define TRCPIDR7 0xFDC
#define TRCPIDR0 0xFE0
#define TRCPIDR1 0xFE4
#define TRCPIDR2 0xFE8
#define TRCPIDR3 0xFEC
#define TRCCIDR0 0xFF0
#define TRCCIDR1 0xFF4
#define TRCCIDR2 0xFF8
#define TRCCIDR3 0xFFC
/* ETMv4 resources */
#define ETM_MAX_NR_PE 8
#define ETMv4_MAX_CNTR 4
#define ETM_MAX_SEQ_STATES 4
#define ETM_MAX_EXT_INP_SEL 4
#define ETM_MAX_EXT_INP 256
#define ETM_MAX_EXT_OUT 4
#define ETM_MAX_SINGLE_ADDR_CMP 16
#define ETM_MAX_ADDR_RANGE_CMP (ETM_MAX_SINGLE_ADDR_CMP / 2)
#define ETM_MAX_DATA_VAL_CMP 8
#define ETMv4_MAX_CTXID_CMP 8
#define ETM_MAX_VMID_CMP 8
#define ETM_MAX_PE_CMP 8
#define ETM_MAX_RES_SEL 16
#define ETM_MAX_SS_CMP 8
#define ETM_ARCH_V4 0x40
#define ETMv4_SYNC_MASK 0x1F
#define ETM_CYC_THRESHOLD_MASK 0xFFF
#define ETMv4_EVENT_MASK 0xFF
#define ETM_CNTR_MAX_VAL 0xFFFF
#define ETM_TRACEID_MASK 0x3f
/* ETMv4 programming modes */
#define ETM_MODE_EXCLUDE BIT(0)
#define ETM_MODE_LOAD BIT(1)
#define ETM_MODE_STORE BIT(2)
#define ETM_MODE_LOAD_STORE BIT(3)
#define ETM_MODE_BB BIT(4)
#define ETMv4_MODE_CYCACC BIT(5)
#define ETMv4_MODE_CTXID BIT(6)
#define ETM_MODE_VMID BIT(7)
#define ETM_MODE_COND(val) BMVAL(val, 8, 10)
#define ETMv4_MODE_TIMESTAMP BIT(11)
#define ETM_MODE_RETURNSTACK BIT(12)
#define ETM_MODE_QELEM(val) BMVAL(val, 13, 14)
#define ETM_MODE_DATA_TRACE_ADDR BIT(15)
#define ETM_MODE_DATA_TRACE_VAL BIT(16)
#define ETM_MODE_ISTALL BIT(17)
#define ETM_MODE_DSTALL BIT(18)
#define ETM_MODE_ATB_TRIGGER BIT(19)
#define ETM_MODE_LPOVERRIDE BIT(20)
#define ETM_MODE_ISTALL_EN BIT(21)
#define ETM_MODE_DSTALL_EN BIT(22)
#define ETM_MODE_INSTPRIO BIT(23)
#define ETM_MODE_NOOVERFLOW BIT(24)
#define ETM_MODE_TRACE_RESET BIT(25)
#define ETM_MODE_TRACE_ERR BIT(26)
#define ETM_MODE_VIEWINST_STARTSTOP BIT(27)
#define ETMv4_MODE_ALL 0xFFFFFFF
#define TRCSTATR_IDLE_BIT 0
/**
* struct etm4_drvdata - specifics associated to an ETM component
* @base: Memory mapped base address for this component.
* @dev: The device entity associated to this component.
* @csdev: Component vitals needed by the framework.
* @spinlock: Only one at a time pls.
* @cpu: The cpu this component is affined to.
* @arch: ETM version number.
* @enable: Is this ETM currently tracing.
* @sticky_enable: true if ETM base configuration has been done.
* @boot_enable:True if we should start tracing at boot time.
* @os_unlock: True if access to management registers is allowed.
* @nr_pe: The number of processing entity available for tracing.
* @nr_pe_cmp: The number of processing entity comparator inputs that are
* available for tracing.
* @nr_addr_cmp:Number of pairs of address comparators available
* as found in ETMIDR4 0-3.
* @nr_cntr: Number of counters as found in ETMIDR5 bit 28-30.
* @nr_ext_inp: Number of external input.
* @numcidc: Number of contextID comparators.
* @numvmidc: Number of VMID comparators.
* @nrseqstate: The number of sequencer states that are implemented.
* @nr_event: Indicates how many events the trace unit support.
* @nr_resource:The number of resource selection pairs available for tracing.
* @nr_ss_cmp: Number of single-shot comparator controls that are available.
* @mode: Controls various modes supported by this ETM.
* @trcid: value of the current ID for this component.
* @trcid_size: Indicates the trace ID width.
* @instrp0: Tracing of load and store instructions
* as P0 elements is supported.
* @trccond: If the trace unit supports conditional
* instruction tracing.
* @retstack: Indicates if the implementation supports a return stack.
* @trc_error: Whether a trace unit can trace a system
* error exception.
* @atbtrig: If the implementation can support ATB triggers
* @lpoverride: If the implementation can support low-power state over.
* @pe_sel: Controls which PE to trace.
* @cfg: Controls the tracing options.
* @eventctrl0: Controls the tracing of arbitrary events.
* @eventctrl1: Controls the behavior of the events that @event_ctrl0 selects.
* @stallctl: If functionality that prevents trace unit buffer overflows
* is available.
* @sysstall: Does the system support stall control of the PE?
* @nooverflow: Indicate if overflow prevention is supported.
* @stall_ctrl: Enables trace unit functionality that prevents trace
* unit buffer overflows.
* @ts_size: Global timestamp size field.
* @ts_ctrl: Controls the insertion of global timestamps in the
* trace streams.
* @syncpr: Indicates if an implementation has a fixed
* synchronization period.
* @syncfreq: Controls how often trace synchronization requests occur.
* @trccci: Indicates if the trace unit supports cycle counting
* for instruction.
* @ccsize: Indicates the size of the cycle counter in bits.
* @ccitmin: minimum value that can be programmed in
* the TRCCCCTLR register.
* @ccctlr: Sets the threshold value for cycle counting.
* @trcbb: Indicates if the trace unit supports branch broadcast tracing.
* @q_support: Q element support characteristics.
* @vinst_ctrl: Controls instruction trace filtering.
* @viiectlr: Set or read, the address range comparators.
* @vissctlr: Set, or read, the single address comparators that control the
* ViewInst start-stop logic.
* @vipcssctlr: Set, or read, which PE comparator inputs can control the
* ViewInst start-stop logic.
* @seq_idx: Sequencor index selector.
* @seq_ctrl: Control for the sequencer state transition control register.
* @seq_rst: Moves the sequencer to state 0 when a programmed event occurs.
* @seq_state: Set, or read the sequencer state.
* @cntr_idx: Counter index seletor.
* @cntrldvr: Sets or returns the reload count value for a counter.
* @cntr_ctrl: Controls the operation of a counter.
* @cntr_val: Sets or returns the value for a counter.
* @res_idx: Resource index selector.
* @res_ctrl: Controls the selection of the resources in the trace unit.
* @ss_ctrl: Controls the corresponding single-shot comparator resource.
* @ss_status: The status of the corresponding single-shot comparator.
* @ss_pe_cmp: Selects the PE comparator inputs for Single-shot control.
* @addr_idx: Address comparator index selector.
* @addr_val: Value for address comparator.
* @addr_acc: Address comparator access type.
* @addr_type: Current status of the comparator register.
* @ctxid_idx: Context ID index selector.
* @ctxid_size: Size of the context ID field to consider.
* @ctxid_val: Value of the context ID comparator.
* @ctxid_mask0:Context ID comparator mask for comparator 0-3.
* @ctxid_mask1:Context ID comparator mask for comparator 4-7.
* @vmid_idx: VM ID index selector.
* @vmid_size: Size of the VM ID comparator to consider.
* @vmid_val: Value of the VM ID comparator.
* @vmid_mask0: VM ID comparator mask for comparator 0-3.
* @vmid_mask1: VM ID comparator mask for comparator 4-7.
* @s_ex_level: In secure state, indicates whether instruction tracing is
* supported for the corresponding Exception level.
* @ns_ex_level:In non-secure state, indicates whether instruction tracing is
* supported for the corresponding Exception level.
* @ext_inp: External input selection.
*/
struct etmv4_drvdata {
void __iomem *base;
struct device *dev;
struct coresight_device *csdev;
spinlock_t spinlock;
int cpu;
u8 arch;
bool enable;
bool sticky_enable;
bool boot_enable;
bool os_unlock;
u8 nr_pe;
u8 nr_pe_cmp;
u8 nr_addr_cmp;
u8 nr_cntr;
u8 nr_ext_inp;
u8 numcidc;
u8 numvmidc;
u8 nrseqstate;
u8 nr_event;
u8 nr_resource;
u8 nr_ss_cmp;
u32 mode;
u8 trcid;
u8 trcid_size;
bool instrp0;
bool trccond;
bool retstack;
bool trc_error;
bool atbtrig;
bool lpoverride;
u32 pe_sel;
u32 cfg;
u32 eventctrl0;
u32 eventctrl1;
bool stallctl;
bool sysstall;
bool nooverflow;
u32 stall_ctrl;
u8 ts_size;
u32 ts_ctrl;
bool syncpr;
u32 syncfreq;
bool trccci;
u8 ccsize;
u8 ccitmin;
u32 ccctlr;
bool trcbb;
u32 bb_ctrl;
bool q_support;
u32 vinst_ctrl;
u32 viiectlr;
u32 vissctlr;
u32 vipcssctlr;
u8 seq_idx;
u32 seq_ctrl[ETM_MAX_SEQ_STATES];
u32 seq_rst;
u32 seq_state;
u8 cntr_idx;
u32 cntrldvr[ETMv4_MAX_CNTR];
u32 cntr_ctrl[ETMv4_MAX_CNTR];
u32 cntr_val[ETMv4_MAX_CNTR];
u8 res_idx;
u32 res_ctrl[ETM_MAX_RES_SEL];
u32 ss_ctrl[ETM_MAX_SS_CMP];
u32 ss_status[ETM_MAX_SS_CMP];
u32 ss_pe_cmp[ETM_MAX_SS_CMP];
u8 addr_idx;
u64 addr_val[ETM_MAX_SINGLE_ADDR_CMP];
u64 addr_acc[ETM_MAX_SINGLE_ADDR_CMP];
u8 addr_type[ETM_MAX_SINGLE_ADDR_CMP];
u8 ctxid_idx;
u8 ctxid_size;
u64 ctxid_val[ETMv4_MAX_CTXID_CMP];
u32 ctxid_mask0;
u32 ctxid_mask1;
u8 vmid_idx;
u8 vmid_size;
u64 vmid_val[ETM_MAX_VMID_CMP];
u32 vmid_mask0;
u32 vmid_mask1;
u8 s_ex_level;
u8 ns_ex_level;
u32 ext_inp;
};
/* Address comparator access types */
enum etm_addr_acctype {
ETM_INSTR_ADDR,
ETM_DATA_LOAD_ADDR,
ETM_DATA_STORE_ADDR,
ETM_DATA_LOAD_STORE_ADDR,
};
/* Address comparator context types */
enum etm_addr_ctxtype {
ETM_CTX_NONE,
ETM_CTX_CTXID,
ETM_CTX_VMID,
ETM_CTX_CTXID_VMID,
};
enum etm_addr_type {
ETM_ADDR_TYPE_NONE,
ETM_ADDR_TYPE_SINGLE,
ETM_ADDR_TYPE_RANGE,
ETM_ADDR_TYPE_START,
ETM_ADDR_TYPE_STOP,
};
#endif

View File

@ -18,9 +18,10 @@
#include <linux/err.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/clk.h>
#include <linux/pm_runtime.h>
#include <linux/coresight.h>
#include <linux/amba/bus.h>
#include <linux/clk.h>
#include "coresight-priv.h"
@ -35,15 +36,15 @@
* struct funnel_drvdata - specifics associated to a funnel component
* @base: memory mapped base address for this component.
* @dev: the device entity associated to this component.
* @atclk: optional clock for the core parts of the funnel.
* @csdev: component vitals needed by the framework.
* @clk: the clock this component is associated to.
* @priority: port selection order.
*/
struct funnel_drvdata {
void __iomem *base;
struct device *dev;
struct clk *atclk;
struct coresight_device *csdev;
struct clk *clk;
unsigned long priority;
};
@ -67,12 +68,8 @@ static int funnel_enable(struct coresight_device *csdev, int inport,
int outport)
{
struct funnel_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
int ret;
ret = clk_prepare_enable(drvdata->clk);
if (ret)
return ret;
pm_runtime_get_sync(drvdata->dev);
funnel_enable_hw(drvdata, inport);
dev_info(drvdata->dev, "FUNNEL inport %d enabled\n", inport);
@ -98,8 +95,7 @@ static void funnel_disable(struct coresight_device *csdev, int inport,
struct funnel_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
funnel_disable_hw(drvdata, inport);
clk_disable_unprepare(drvdata->clk);
pm_runtime_put(drvdata->dev);
dev_info(drvdata->dev, "FUNNEL inport %d disabled\n", inport);
}
@ -153,16 +149,14 @@ static u32 get_funnel_ctrl_hw(struct funnel_drvdata *drvdata)
static ssize_t funnel_ctrl_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
int ret;
u32 val;
struct funnel_drvdata *drvdata = dev_get_drvdata(dev->parent);
ret = clk_prepare_enable(drvdata->clk);
if (ret)
return ret;
pm_runtime_get_sync(drvdata->dev);
val = get_funnel_ctrl_hw(drvdata);
clk_disable_unprepare(drvdata->clk);
pm_runtime_put(drvdata->dev);
return sprintf(buf, "%#x\n", val);
}
@ -177,6 +171,7 @@ ATTRIBUTE_GROUPS(coresight_funnel);
static int funnel_probe(struct amba_device *adev, const struct amba_id *id)
{
int ret;
void __iomem *base;
struct device *dev = &adev->dev;
struct coresight_platform_data *pdata = NULL;
@ -197,6 +192,12 @@ static int funnel_probe(struct amba_device *adev, const struct amba_id *id)
return -ENOMEM;
drvdata->dev = &adev->dev;
drvdata->atclk = devm_clk_get(&adev->dev, "atclk"); /* optional */
if (!IS_ERR(drvdata->atclk)) {
ret = clk_prepare_enable(drvdata->atclk);
if (ret)
return ret;
}
dev_set_drvdata(dev, drvdata);
/* Validity for the resource is already checked by the AMBA core */
@ -205,8 +206,7 @@ static int funnel_probe(struct amba_device *adev, const struct amba_id *id)
return PTR_ERR(base);
drvdata->base = base;
drvdata->clk = adev->pclk;
pm_runtime_put(&adev->dev);
desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL);
if (!desc)
@ -234,6 +234,32 @@ static int funnel_remove(struct amba_device *adev)
return 0;
}
#ifdef CONFIG_PM
static int funnel_runtime_suspend(struct device *dev)
{
struct funnel_drvdata *drvdata = dev_get_drvdata(dev);
if (drvdata && !IS_ERR(drvdata->atclk))
clk_disable_unprepare(drvdata->atclk);
return 0;
}
static int funnel_runtime_resume(struct device *dev)
{
struct funnel_drvdata *drvdata = dev_get_drvdata(dev);
if (drvdata && !IS_ERR(drvdata->atclk))
clk_prepare_enable(drvdata->atclk);
return 0;
}
#endif
static const struct dev_pm_ops funnel_dev_pm_ops = {
SET_RUNTIME_PM_OPS(funnel_runtime_suspend, funnel_runtime_resume, NULL)
};
static struct amba_id funnel_ids[] = {
{
.id = 0x0003b908,
@ -246,6 +272,7 @@ static struct amba_driver funnel_driver = {
.drv = {
.name = "coresight-funnel",
.owner = THIS_MODULE,
.pm = &funnel_dev_pm_ops,
},
.probe = funnel_probe,
.remove = funnel_remove,

View File

@ -0,0 +1,215 @@
/*
* Copyright (c) 2011-2015, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/amba/bus.h>
#include <linux/clk.h>
#include <linux/coresight.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/of.h>
#include <linux/pm_runtime.h>
#include <linux/slab.h>
#include "coresight-priv.h"
#define REPLICATOR_IDFILTER0 0x000
#define REPLICATOR_IDFILTER1 0x004
/**
* struct replicator_state - specifics associated to a replicator component
* @base: memory mapped base address for this component.
* @dev: the device entity associated with this component
* @atclk: optional clock for the core parts of the replicator.
* @csdev: component vitals needed by the framework
*/
struct replicator_state {
void __iomem *base;
struct device *dev;
struct clk *atclk;
struct coresight_device *csdev;
};
static int replicator_enable(struct coresight_device *csdev, int inport,
int outport)
{
struct replicator_state *drvdata = dev_get_drvdata(csdev->dev.parent);
pm_runtime_get_sync(drvdata->dev);
CS_UNLOCK(drvdata->base);
/*
* Ensure that the other port is disabled
* 0x00 - passing through the replicator unimpeded
* 0xff - disable (or impede) the flow of ATB data
*/
if (outport == 0) {
writel_relaxed(0x00, drvdata->base + REPLICATOR_IDFILTER0);
writel_relaxed(0xff, drvdata->base + REPLICATOR_IDFILTER1);
} else {
writel_relaxed(0x00, drvdata->base + REPLICATOR_IDFILTER1);
writel_relaxed(0xff, drvdata->base + REPLICATOR_IDFILTER0);
}
CS_LOCK(drvdata->base);
dev_info(drvdata->dev, "REPLICATOR enabled\n");
return 0;
}
static void replicator_disable(struct coresight_device *csdev, int inport,
int outport)
{
struct replicator_state *drvdata = dev_get_drvdata(csdev->dev.parent);
CS_UNLOCK(drvdata->base);
/* disable the flow of ATB data through port */
if (outport == 0)
writel_relaxed(0xff, drvdata->base + REPLICATOR_IDFILTER0);
else
writel_relaxed(0xff, drvdata->base + REPLICATOR_IDFILTER1);
CS_LOCK(drvdata->base);
pm_runtime_put(drvdata->dev);
dev_info(drvdata->dev, "REPLICATOR disabled\n");
}
static const struct coresight_ops_link replicator_link_ops = {
.enable = replicator_enable,
.disable = replicator_disable,
};
static const struct coresight_ops replicator_cs_ops = {
.link_ops = &replicator_link_ops,
};
static int replicator_probe(struct amba_device *adev, const struct amba_id *id)
{
int ret;
struct device *dev = &adev->dev;
struct resource *res = &adev->res;
struct coresight_platform_data *pdata = NULL;
struct replicator_state *drvdata;
struct coresight_desc *desc;
struct device_node *np = adev->dev.of_node;
void __iomem *base;
if (np) {
pdata = of_get_coresight_platform_data(dev, np);
if (IS_ERR(pdata))
return PTR_ERR(pdata);
adev->dev.platform_data = pdata;
}
drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL);
if (!drvdata)
return -ENOMEM;
drvdata->dev = &adev->dev;
drvdata->atclk = devm_clk_get(&adev->dev, "atclk"); /* optional */
if (!IS_ERR(drvdata->atclk)) {
ret = clk_prepare_enable(drvdata->atclk);
if (ret)
return ret;
}
/* Validity for the resource is already checked by the AMBA core */
base = devm_ioremap_resource(dev, res);
if (IS_ERR(base))
return PTR_ERR(base);
drvdata->base = base;
dev_set_drvdata(dev, drvdata);
pm_runtime_put(&adev->dev);
desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL);
if (!desc)
return -ENOMEM;
desc->type = CORESIGHT_DEV_TYPE_LINK;
desc->subtype.link_subtype = CORESIGHT_DEV_SUBTYPE_LINK_SPLIT;
desc->ops = &replicator_cs_ops;
desc->pdata = adev->dev.platform_data;
desc->dev = &adev->dev;
drvdata->csdev = coresight_register(desc);
if (IS_ERR(drvdata->csdev))
return PTR_ERR(drvdata->csdev);
dev_info(dev, "%s initialized\n", (char *)id->data);
return 0;
}
static int replicator_remove(struct amba_device *adev)
{
struct replicator_state *drvdata = amba_get_drvdata(adev);
pm_runtime_disable(&adev->dev);
coresight_unregister(drvdata->csdev);
return 0;
}
#ifdef CONFIG_PM
static int replicator_runtime_suspend(struct device *dev)
{
struct replicator_state *drvdata = dev_get_drvdata(dev);
if (drvdata && !IS_ERR(drvdata->atclk))
clk_disable_unprepare(drvdata->atclk);
return 0;
}
static int replicator_runtime_resume(struct device *dev)
{
struct replicator_state *drvdata = dev_get_drvdata(dev);
if (drvdata && !IS_ERR(drvdata->atclk))
clk_prepare_enable(drvdata->atclk);
return 0;
}
#endif
static const struct dev_pm_ops replicator_dev_pm_ops = {
SET_RUNTIME_PM_OPS(replicator_runtime_suspend,
replicator_runtime_resume,
NULL)
};
static struct amba_id replicator_ids[] = {
{
.id = 0x0003b909,
.mask = 0x0003ffff,
.data = "REPLICATOR 1.0",
},
{ 0, 0 },
};
static struct amba_driver replicator_driver = {
.drv = {
.name = "coresight-replicator-qcom",
.pm = &replicator_dev_pm_ops,
},
.probe = replicator_probe,
.remove = replicator_remove,
.id_table = replicator_ids,
};
module_amba_driver(replicator_driver);

View File

@ -18,6 +18,7 @@
#include <linux/io.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/pm_runtime.h>
#include <linux/clk.h>
#include <linux/of.h>
#include <linux/coresight.h>
@ -27,10 +28,12 @@
/**
* struct replicator_drvdata - specifics associated to a replicator component
* @dev: the device entity associated with this component
* @atclk: optional clock for the core parts of the replicator.
* @csdev: component vitals needed by the framework
*/
struct replicator_drvdata {
struct device *dev;
struct clk *atclk;
struct coresight_device *csdev;
};
@ -39,6 +42,7 @@ static int replicator_enable(struct coresight_device *csdev, int inport,
{
struct replicator_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
pm_runtime_get_sync(drvdata->dev);
dev_info(drvdata->dev, "REPLICATOR enabled\n");
return 0;
}
@ -48,6 +52,7 @@ static void replicator_disable(struct coresight_device *csdev, int inport,
{
struct replicator_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
pm_runtime_put(drvdata->dev);
dev_info(drvdata->dev, "REPLICATOR disabled\n");
}
@ -62,6 +67,7 @@ static const struct coresight_ops replicator_cs_ops = {
static int replicator_probe(struct platform_device *pdev)
{
int ret;
struct device *dev = &pdev->dev;
struct coresight_platform_data *pdata = NULL;
struct replicator_drvdata *drvdata;
@ -80,11 +86,22 @@ static int replicator_probe(struct platform_device *pdev)
return -ENOMEM;
drvdata->dev = &pdev->dev;
drvdata->atclk = devm_clk_get(&pdev->dev, "atclk"); /* optional */
if (!IS_ERR(drvdata->atclk)) {
ret = clk_prepare_enable(drvdata->atclk);
if (ret)
return ret;
}
pm_runtime_get_noresume(&pdev->dev);
pm_runtime_set_active(&pdev->dev);
pm_runtime_enable(&pdev->dev);
platform_set_drvdata(pdev, drvdata);
desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL);
if (!desc)
return -ENOMEM;
if (!desc) {
ret = -ENOMEM;
goto out_disable_pm;
}
desc->type = CORESIGHT_DEV_TYPE_LINK;
desc->subtype.link_subtype = CORESIGHT_DEV_SUBTYPE_LINK_SPLIT;
@ -92,11 +109,23 @@ static int replicator_probe(struct platform_device *pdev)
desc->pdata = pdev->dev.platform_data;
desc->dev = &pdev->dev;
drvdata->csdev = coresight_register(desc);
if (IS_ERR(drvdata->csdev))
return PTR_ERR(drvdata->csdev);
if (IS_ERR(drvdata->csdev)) {
ret = PTR_ERR(drvdata->csdev);
goto out_disable_pm;
}
pm_runtime_put(&pdev->dev);
dev_info(dev, "REPLICATOR initialized\n");
return 0;
out_disable_pm:
if (!IS_ERR(drvdata->atclk))
clk_disable_unprepare(drvdata->atclk);
pm_runtime_put_noidle(&pdev->dev);
pm_runtime_disable(&pdev->dev);
return ret;
}
static int replicator_remove(struct platform_device *pdev)
@ -104,9 +133,42 @@ static int replicator_remove(struct platform_device *pdev)
struct replicator_drvdata *drvdata = platform_get_drvdata(pdev);
coresight_unregister(drvdata->csdev);
pm_runtime_get_sync(&pdev->dev);
if (!IS_ERR(drvdata->atclk))
clk_disable_unprepare(drvdata->atclk);
pm_runtime_put_noidle(&pdev->dev);
pm_runtime_disable(&pdev->dev);
return 0;
}
#ifdef CONFIG_PM
static int replicator_runtime_suspend(struct device *dev)
{
struct replicator_drvdata *drvdata = dev_get_drvdata(dev);
if (drvdata && !IS_ERR(drvdata->atclk))
clk_disable_unprepare(drvdata->atclk);
return 0;
}
static int replicator_runtime_resume(struct device *dev)
{
struct replicator_drvdata *drvdata = dev_get_drvdata(dev);
if (drvdata && !IS_ERR(drvdata->atclk))
clk_prepare_enable(drvdata->atclk);
return 0;
}
#endif
static const struct dev_pm_ops replicator_dev_pm_ops = {
SET_RUNTIME_PM_OPS(replicator_runtime_suspend,
replicator_runtime_resume, NULL)
};
static const struct of_device_id replicator_match[] = {
{.compatible = "arm,coresight-replicator"},
{}
@ -118,6 +180,7 @@ static struct platform_driver replicator_driver = {
.driver = {
.name = "coresight-replicator",
.of_match_table = replicator_match,
.pm = &replicator_dev_pm_ops,
},
};

View File

@ -23,7 +23,7 @@
#include <linux/slab.h>
#include <linux/dma-mapping.h>
#include <linux/spinlock.h>
#include <linux/clk.h>
#include <linux/pm_runtime.h>
#include <linux/of.h>
#include <linux/coresight.h>
#include <linux/amba/bus.h>
@ -104,7 +104,6 @@ enum tmc_mem_intf_width {
* @dev: the device entity associated to this component.
* @csdev: component vitals needed by the framework.
* @miscdev: specifics to handle "/dev/xyz.tmc" entry.
* @clk: the clock this component is associated to.
* @spinlock: only one at a time pls.
* @read_count: manages preparation of buffer for reading.
* @buf: area of memory where trace data get sent.
@ -120,7 +119,6 @@ struct tmc_drvdata {
struct device *dev;
struct coresight_device *csdev;
struct miscdevice miscdev;
struct clk *clk;
spinlock_t spinlock;
int read_count;
bool reading;
@ -242,17 +240,14 @@ static void tmc_etf_enable_hw(struct tmc_drvdata *drvdata)
static int tmc_enable(struct tmc_drvdata *drvdata, enum tmc_mode mode)
{
int ret;
unsigned long flags;
ret = clk_prepare_enable(drvdata->clk);
if (ret)
return ret;
pm_runtime_get_sync(drvdata->dev);
spin_lock_irqsave(&drvdata->spinlock, flags);
if (drvdata->reading) {
spin_unlock_irqrestore(&drvdata->spinlock, flags);
clk_disable_unprepare(drvdata->clk);
pm_runtime_put(drvdata->dev);
return -EBUSY;
}
@ -386,7 +381,7 @@ out:
drvdata->enable = false;
spin_unlock_irqrestore(&drvdata->spinlock, flags);
clk_disable_unprepare(drvdata->clk);
pm_runtime_put(drvdata->dev);
dev_info(drvdata->dev, "TMC disabled\n");
}
@ -568,17 +563,13 @@ static const struct file_operations tmc_fops = {
static ssize_t status_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
int ret;
unsigned long flags;
u32 tmc_rsz, tmc_sts, tmc_rrp, tmc_rwp, tmc_trg;
u32 tmc_ctl, tmc_ffsr, tmc_ffcr, tmc_mode, tmc_pscr;
u32 devid;
struct tmc_drvdata *drvdata = dev_get_drvdata(dev->parent);
ret = clk_prepare_enable(drvdata->clk);
if (ret)
goto out;
pm_runtime_get_sync(drvdata->dev);
spin_lock_irqsave(&drvdata->spinlock, flags);
CS_UNLOCK(drvdata->base);
@ -596,8 +587,7 @@ static ssize_t status_show(struct device *dev,
CS_LOCK(drvdata->base);
spin_unlock_irqrestore(&drvdata->spinlock, flags);
clk_disable_unprepare(drvdata->clk);
pm_runtime_put(drvdata->dev);
return sprintf(buf,
"Depth:\t\t0x%x\n"
@ -613,7 +603,7 @@ static ssize_t status_show(struct device *dev,
"DEVID:\t\t0x%x\n",
tmc_rsz, tmc_sts, tmc_rrp, tmc_rwp, tmc_trg,
tmc_ctl, tmc_ffsr, tmc_ffcr, tmc_mode, tmc_pscr, devid);
out:
return -EINVAL;
}
static DEVICE_ATTR_RO(status);
@ -700,11 +690,6 @@ static int tmc_probe(struct amba_device *adev, const struct amba_id *id)
spin_lock_init(&drvdata->spinlock);
drvdata->clk = adev->pclk;
ret = clk_prepare_enable(drvdata->clk);
if (ret)
return ret;
devid = readl_relaxed(drvdata->base + CORESIGHT_DEVID);
drvdata->config_type = BMVAL(devid, 6, 7);
@ -719,7 +704,7 @@ static int tmc_probe(struct amba_device *adev, const struct amba_id *id)
drvdata->size = readl_relaxed(drvdata->base + TMC_RSZ) * 4;
}
clk_disable_unprepare(drvdata->clk);
pm_runtime_put(&adev->dev);
if (drvdata->config_type == TMC_CONFIG_TYPE_ETR) {
drvdata->vaddr = dma_alloc_coherent(dev, drvdata->size,

View File

@ -17,9 +17,10 @@
#include <linux/io.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/clk.h>
#include <linux/pm_runtime.h>
#include <linux/coresight.h>
#include <linux/amba/bus.h>
#include <linux/clk.h>
#include "coresight-priv.h"
@ -50,14 +51,14 @@
/**
* @base: memory mapped base address for this component.
* @dev: the device entity associated to this component.
* @atclk: optional clock for the core parts of the TPIU.
* @csdev: component vitals needed by the framework.
* @clk: the clock this component is associated to.
*/
struct tpiu_drvdata {
void __iomem *base;
struct device *dev;
struct clk *atclk;
struct coresight_device *csdev;
struct clk *clk;
};
static void tpiu_enable_hw(struct tpiu_drvdata *drvdata)
@ -72,12 +73,8 @@ static void tpiu_enable_hw(struct tpiu_drvdata *drvdata)
static int tpiu_enable(struct coresight_device *csdev)
{
struct tpiu_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
int ret;
ret = clk_prepare_enable(drvdata->clk);
if (ret)
return ret;
pm_runtime_get_sync(csdev->dev.parent);
tpiu_enable_hw(drvdata);
dev_info(drvdata->dev, "TPIU enabled\n");
@ -101,8 +98,7 @@ static void tpiu_disable(struct coresight_device *csdev)
struct tpiu_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
tpiu_disable_hw(drvdata);
clk_disable_unprepare(drvdata->clk);
pm_runtime_put(csdev->dev.parent);
dev_info(drvdata->dev, "TPIU disabled\n");
}
@ -139,6 +135,12 @@ static int tpiu_probe(struct amba_device *adev, const struct amba_id *id)
return -ENOMEM;
drvdata->dev = &adev->dev;
drvdata->atclk = devm_clk_get(&adev->dev, "atclk"); /* optional */
if (!IS_ERR(drvdata->atclk)) {
ret = clk_prepare_enable(drvdata->atclk);
if (ret)
return ret;
}
dev_set_drvdata(dev, drvdata);
/* Validity for the resource is already checked by the AMBA core */
@ -148,15 +150,10 @@ static int tpiu_probe(struct amba_device *adev, const struct amba_id *id)
drvdata->base = base;
drvdata->clk = adev->pclk;
ret = clk_prepare_enable(drvdata->clk);
if (ret)
return ret;
/* Disable tpiu to support older devices */
tpiu_disable_hw(drvdata);
clk_disable_unprepare(drvdata->clk);
pm_runtime_put(&adev->dev);
desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL);
if (!desc)
@ -183,11 +180,41 @@ static int tpiu_remove(struct amba_device *adev)
return 0;
}
#ifdef CONFIG_PM
static int tpiu_runtime_suspend(struct device *dev)
{
struct tpiu_drvdata *drvdata = dev_get_drvdata(dev);
if (drvdata && !IS_ERR(drvdata->atclk))
clk_disable_unprepare(drvdata->atclk);
return 0;
}
static int tpiu_runtime_resume(struct device *dev)
{
struct tpiu_drvdata *drvdata = dev_get_drvdata(dev);
if (drvdata && !IS_ERR(drvdata->atclk))
clk_prepare_enable(drvdata->atclk);
return 0;
}
#endif
static const struct dev_pm_ops tpiu_dev_pm_ops = {
SET_RUNTIME_PM_OPS(tpiu_runtime_suspend, tpiu_runtime_resume, NULL)
};
static struct amba_id tpiu_ids[] = {
{
.id = 0x0003b912,
.mask = 0x0003ffff,
},
{
.id = 0x0004b912,
.mask = 0x0007ffff,
},
{ 0, 0},
};
@ -195,6 +222,7 @@ static struct amba_driver tpiu_driver = {
.drv = {
.name = "coresight-tpiu",
.owner = THIS_MODULE,
.pm = &tpiu_dev_pm_ops,
},
.probe = tpiu_probe,
.remove = tpiu_remove,

View File

@ -37,7 +37,7 @@ of_coresight_get_endpoint_device(struct device_node *endpoint)
struct device *dev = NULL;
/*
* If we have a non-configuable replicator, it will be found on the
* If we have a non-configurable replicator, it will be found on the
* platform bus.
*/
dev = bus_find_device(&platform_bus_type, NULL,

View File

@ -46,6 +46,9 @@ struct i2c_par {
static LIST_HEAD(adapter_list);
static DEFINE_MUTEX(adapter_list_lock);
#define MAX_DEVICE 4
static int parport[MAX_DEVICE] = {0, -1, -1, -1};
/* ----- Low-level parallel port access ----------------------------------- */
@ -163,17 +166,34 @@ static void i2c_parport_irq(void *data)
static void i2c_parport_attach(struct parport *port)
{
struct i2c_par *adapter;
int i;
struct pardev_cb i2c_parport_cb;
for (i = 0; i < MAX_DEVICE; i++) {
if (parport[i] == -1)
continue;
if (port->number == parport[i])
break;
}
if (i == MAX_DEVICE) {
pr_debug("i2c-parport: Not using parport%d.\n", port->number);
return;
}
adapter = kzalloc(sizeof(struct i2c_par), GFP_KERNEL);
if (adapter == NULL) {
printk(KERN_ERR "i2c-parport: Failed to kzalloc\n");
return;
}
memset(&i2c_parport_cb, 0, sizeof(i2c_parport_cb));
i2c_parport_cb.flags = PARPORT_FLAG_EXCL;
i2c_parport_cb.irq_func = i2c_parport_irq;
i2c_parport_cb.private = adapter;
pr_debug("i2c-parport: attaching to %s\n", port->name);
parport_disable_irq(port);
adapter->pdev = parport_register_device(port, "i2c-parport",
NULL, NULL, i2c_parport_irq, PARPORT_FLAG_EXCL, adapter);
adapter->pdev = parport_register_dev_model(port, "i2c-parport",
&i2c_parport_cb, i);
if (!adapter->pdev) {
printk(KERN_ERR "i2c-parport: Unable to register with parport\n");
goto err_free;
@ -267,9 +287,10 @@ static void i2c_parport_detach(struct parport *port)
}
static struct parport_driver i2c_parport_driver = {
.name = "i2c-parport",
.attach = i2c_parport_attach,
.detach = i2c_parport_detach,
.name = "i2c-parport",
.match_port = i2c_parport_attach,
.detach = i2c_parport_detach,
.devmodel = true,
};
/* ----- Module loading, unloading and information ------------------------ */
@ -298,5 +319,12 @@ MODULE_AUTHOR("Jean Delvare <jdelvare@suse.de>");
MODULE_DESCRIPTION("I2C bus over parallel port");
MODULE_LICENSE("GPL");
module_param_array(parport, int, NULL, 0);
MODULE_PARM_DESC(parport,
"List of parallel ports to bind to, by index.\n"
" Atmost " __stringify(MAX_DEVICE) " devices are supported.\n"
" Default is one device connected to parport0.\n"
);
module_init(i2c_parport_init);
module_exit(i2c_parport_exit);

View File

@ -520,7 +520,6 @@ source "drivers/misc/eeprom/Kconfig"
source "drivers/misc/cb710/Kconfig"
source "drivers/misc/ti-st/Kconfig"
source "drivers/misc/lis3lv02d/Kconfig"
source "drivers/misc/carma/Kconfig"
source "drivers/misc/altera-stapl/Kconfig"
source "drivers/misc/mei/Kconfig"
source "drivers/misc/vmw_vmci/Kconfig"

View File

@ -44,7 +44,6 @@ obj-$(CONFIG_ARM_CHARLCD) += arm-charlcd.o
obj-$(CONFIG_PCH_PHUB) += pch_phub.o
obj-y += ti-st/
obj-y += lis3lv02d/
obj-y += carma/
obj-$(CONFIG_USB_SWITCH_FSA9480) += fsa9480.o
obj-$(CONFIG_ALTERA_STAPL) +=altera-stapl/
obj-$(CONFIG_INTEL_MEI) += mei/

View File

@ -1,15 +0,0 @@
config CARMA_FPGA
tristate "CARMA DATA-FPGA Access Driver"
depends on FSL_SOC && PPC_83xx && HAS_DMA && FSL_DMA
default n
help
Say Y here to include support for communicating with the data
processing FPGAs on the OVRO CARMA board.
config CARMA_FPGA_PROGRAM
tristate "CARMA DATA-FPGA Programmer"
depends on FSL_SOC && PPC_83xx && HAS_DMA && FSL_DMA
default n
help
Say Y here to include support for programming the data processing
FPGAs on the OVRO CARMA board.

View File

@ -1,2 +0,0 @@
obj-$(CONFIG_CARMA_FPGA) += carma-fpga.o
obj-$(CONFIG_CARMA_FPGA_PROGRAM) += carma-fpga-program.o

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -59,46 +59,29 @@ void mei_amthif_reset_params(struct mei_device *dev)
* mei_amthif_host_init - mei initialization amthif client.
*
* @dev: the device structure
* @me_cl: me client
*
* Return: 0 on success, <0 on failure.
*/
int mei_amthif_host_init(struct mei_device *dev)
int mei_amthif_host_init(struct mei_device *dev, struct mei_me_client *me_cl)
{
struct mei_cl *cl = &dev->iamthif_cl;
struct mei_me_client *me_cl;
int ret;
dev->iamthif_state = MEI_IAMTHIF_IDLE;
mei_cl_init(cl, dev);
me_cl = mei_me_cl_by_uuid(dev, &mei_amthif_guid);
if (!me_cl) {
dev_info(dev->dev, "amthif: failed to find the client");
return -ENOTTY;
}
cl->me_client_id = me_cl->client_id;
cl->cl_uuid = me_cl->props.protocol_name;
/* Assign iamthif_mtu to the value received from ME */
dev->iamthif_mtu = me_cl->props.max_msg_length;
dev_dbg(dev->dev, "IAMTHIF_MTU = %d\n", dev->iamthif_mtu);
ret = mei_cl_link(cl, MEI_IAMTHIF_HOST_CLIENT_ID);
if (ret < 0) {
dev_err(dev->dev, "amthif: failed cl_link %d\n", ret);
goto out;
return ret;
}
ret = mei_cl_connect(cl, NULL);
ret = mei_cl_connect(cl, me_cl, NULL);
dev->iamthif_state = MEI_IAMTHIF_IDLE;
out:
mei_me_cl_put(me_cl);
return ret;
}
@ -250,7 +233,6 @@ static int mei_amthif_read_start(struct mei_cl *cl, struct file *file)
{
struct mei_device *dev = cl->dev;
struct mei_cl_cb *cb;
size_t length = dev->iamthif_mtu;
int rets;
cb = mei_io_cb_init(cl, MEI_FOP_READ, file);
@ -259,7 +241,7 @@ static int mei_amthif_read_start(struct mei_cl *cl, struct file *file)
goto err;
}
rets = mei_io_cb_alloc_buf(cb, length);
rets = mei_io_cb_alloc_buf(cb, mei_cl_mtu(cl));
if (rets)
goto err;

View File

@ -35,18 +35,30 @@ static int mei_cl_device_match(struct device *dev, struct device_driver *drv)
struct mei_cl_device *device = to_mei_cl_device(dev);
struct mei_cl_driver *driver = to_mei_cl_driver(drv);
const struct mei_cl_device_id *id;
const uuid_le *uuid;
const char *name;
if (!device)
return 0;
uuid = mei_me_cl_uuid(device->me_cl);
name = device->name;
if (!driver || !driver->id_table)
return 0;
id = driver->id_table;
while (id->name[0]) {
if (!strncmp(dev_name(dev), id->name, sizeof(id->name)))
return 1;
while (uuid_le_cmp(NULL_UUID_LE, id->uuid)) {
if (!uuid_le_cmp(*uuid, id->uuid)) {
if (id->name[0]) {
if (!strncmp(name, id->name, sizeof(id->name)))
return 1;
} else {
return 1;
}
}
id++;
}
@ -69,7 +81,7 @@ static int mei_cl_device_probe(struct device *dev)
dev_dbg(dev, "Device probe\n");
strlcpy(id.name, dev_name(dev), sizeof(id.name));
strlcpy(id.name, device->name, sizeof(id.name));
return driver->probe(device, &id);
}
@ -97,18 +109,48 @@ static int mei_cl_device_remove(struct device *dev)
return driver->remove(device);
}
static ssize_t name_show(struct device *dev, struct device_attribute *a,
char *buf)
{
struct mei_cl_device *device = to_mei_cl_device(dev);
size_t len;
len = snprintf(buf, PAGE_SIZE, "%s", device->name);
return (len >= PAGE_SIZE) ? (PAGE_SIZE - 1) : len;
}
static DEVICE_ATTR_RO(name);
static ssize_t uuid_show(struct device *dev, struct device_attribute *a,
char *buf)
{
struct mei_cl_device *device = to_mei_cl_device(dev);
const uuid_le *uuid = mei_me_cl_uuid(device->me_cl);
size_t len;
len = snprintf(buf, PAGE_SIZE, "%pUl", uuid);
return (len >= PAGE_SIZE) ? (PAGE_SIZE - 1) : len;
}
static DEVICE_ATTR_RO(uuid);
static ssize_t modalias_show(struct device *dev, struct device_attribute *a,
char *buf)
{
int len;
struct mei_cl_device *device = to_mei_cl_device(dev);
const uuid_le *uuid = mei_me_cl_uuid(device->me_cl);
size_t len;
len = snprintf(buf, PAGE_SIZE, "mei:%s\n", dev_name(dev));
len = snprintf(buf, PAGE_SIZE, "mei:%s:" MEI_CL_UUID_FMT ":",
device->name, MEI_CL_UUID_ARGS(uuid->b));
return (len >= PAGE_SIZE) ? (PAGE_SIZE - 1) : len;
}
static DEVICE_ATTR_RO(modalias);
static struct attribute *mei_cl_dev_attrs[] = {
&dev_attr_name.attr,
&dev_attr_uuid.attr,
&dev_attr_modalias.attr,
NULL,
};
@ -116,7 +158,17 @@ ATTRIBUTE_GROUPS(mei_cl_dev);
static int mei_cl_uevent(struct device *dev, struct kobj_uevent_env *env)
{
if (add_uevent_var(env, "MODALIAS=mei:%s", dev_name(dev)))
struct mei_cl_device *device = to_mei_cl_device(dev);
const uuid_le *uuid = mei_me_cl_uuid(device->me_cl);
if (add_uevent_var(env, "MEI_CL_UUID=%pUl", uuid))
return -ENOMEM;
if (add_uevent_var(env, "MEI_CL_NAME=%s", device->name))
return -ENOMEM;
if (add_uevent_var(env, "MODALIAS=mei:%s:" MEI_CL_UUID_FMT ":",
device->name, MEI_CL_UUID_ARGS(uuid->b)))
return -ENOMEM;
return 0;
@ -133,7 +185,13 @@ static struct bus_type mei_cl_bus_type = {
static void mei_cl_dev_release(struct device *dev)
{
kfree(to_mei_cl_device(dev));
struct mei_cl_device *device = to_mei_cl_device(dev);
if (!device)
return;
mei_me_cl_put(device->me_cl);
kfree(device);
}
static struct device_type mei_cl_device_type = {
@ -141,45 +199,50 @@ static struct device_type mei_cl_device_type = {
};
struct mei_cl *mei_cl_bus_find_cl_by_uuid(struct mei_device *dev,
uuid_le uuid)
uuid_le uuid)
{
struct mei_cl *cl;
list_for_each_entry(cl, &dev->device_list, device_link) {
if (!uuid_le_cmp(uuid, cl->cl_uuid))
if (cl->device && cl->device->me_cl &&
!uuid_le_cmp(uuid, *mei_me_cl_uuid(cl->device->me_cl)))
return cl;
}
return NULL;
}
struct mei_cl_device *mei_cl_add_device(struct mei_device *dev,
uuid_le uuid, char *name,
struct mei_cl_ops *ops)
struct mei_me_client *me_cl,
struct mei_cl *cl,
char *name)
{
struct mei_cl_device *device;
struct mei_cl *cl;
int status;
cl = mei_cl_bus_find_cl_by_uuid(dev, uuid);
if (cl == NULL)
return NULL;
device = kzalloc(sizeof(struct mei_cl_device), GFP_KERNEL);
if (!device)
return NULL;
device->cl = cl;
device->ops = ops;
device->me_cl = mei_me_cl_get(me_cl);
if (!device->me_cl) {
kfree(device);
return NULL;
}
device->cl = cl;
device->dev.parent = dev->dev;
device->dev.bus = &mei_cl_bus_type;
device->dev.type = &mei_cl_device_type;
dev_set_name(&device->dev, "%s", name);
strlcpy(device->name, name, sizeof(device->name));
dev_set_name(&device->dev, "mei:%s:%pUl", name, mei_me_cl_uuid(me_cl));
status = device_register(&device->dev);
if (status) {
dev_err(dev->dev, "Failed to register MEI device\n");
mei_me_cl_put(device->me_cl);
kfree(device);
return NULL;
}
@ -224,11 +287,10 @@ void mei_cl_driver_unregister(struct mei_cl_driver *driver)
}
EXPORT_SYMBOL_GPL(mei_cl_driver_unregister);
static ssize_t ___mei_cl_send(struct mei_cl *cl, u8 *buf, size_t length,
ssize_t __mei_cl_send(struct mei_cl *cl, u8 *buf, size_t length,
bool blocking)
{
struct mei_device *dev;
struct mei_me_client *me_cl = NULL;
struct mei_cl_cb *cb = NULL;
ssize_t rets;
@ -244,13 +306,12 @@ static ssize_t ___mei_cl_send(struct mei_cl *cl, u8 *buf, size_t length,
}
/* Check if we have an ME client device */
me_cl = mei_me_cl_by_uuid_id(dev, &cl->cl_uuid, cl->me_client_id);
if (!me_cl) {
if (!mei_me_cl_is_active(cl->me_cl)) {
rets = -ENOTTY;
goto out;
}
if (length > me_cl->props.max_msg_length) {
if (length > mei_cl_mtu(cl)) {
rets = -EFBIG;
goto out;
}
@ -266,7 +327,6 @@ static ssize_t ___mei_cl_send(struct mei_cl *cl, u8 *buf, size_t length,
rets = mei_cl_write(cl, cb, blocking);
out:
mei_me_cl_put(me_cl);
mutex_unlock(&dev->device_lock);
if (rets < 0)
mei_io_cb_free(cb);
@ -341,16 +401,6 @@ out:
return rets;
}
inline ssize_t __mei_cl_async_send(struct mei_cl *cl, u8 *buf, size_t length)
{
return ___mei_cl_send(cl, buf, length, 0);
}
inline ssize_t __mei_cl_send(struct mei_cl *cl, u8 *buf, size_t length)
{
return ___mei_cl_send(cl, buf, length, 1);
}
ssize_t mei_cl_send(struct mei_cl_device *device, u8 *buf, size_t length)
{
struct mei_cl *cl = device->cl;
@ -358,23 +408,17 @@ ssize_t mei_cl_send(struct mei_cl_device *device, u8 *buf, size_t length)
if (cl == NULL)
return -ENODEV;
if (device->ops && device->ops->send)
return device->ops->send(device, buf, length);
return __mei_cl_send(cl, buf, length);
return __mei_cl_send(cl, buf, length, 1);
}
EXPORT_SYMBOL_GPL(mei_cl_send);
ssize_t mei_cl_recv(struct mei_cl_device *device, u8 *buf, size_t length)
{
struct mei_cl *cl = device->cl;
struct mei_cl *cl = device->cl;
if (cl == NULL)
return -ENODEV;
if (device->ops && device->ops->recv)
return device->ops->recv(device, buf, length);
return __mei_cl_recv(cl, buf, length);
}
EXPORT_SYMBOL_GPL(mei_cl_recv);
@ -436,7 +480,13 @@ int mei_cl_enable_device(struct mei_cl_device *device)
mutex_lock(&dev->device_lock);
err = mei_cl_connect(cl, NULL);
if (mei_cl_is_connected(cl)) {
mutex_unlock(&dev->device_lock);
dev_warn(dev->dev, "Already connected");
return -EBUSY;
}
err = mei_cl_connect(cl, device->me_cl, NULL);
if (err < 0) {
mutex_unlock(&dev->device_lock);
dev_err(dev->dev, "Could not connect to the ME client");
@ -449,10 +499,7 @@ int mei_cl_enable_device(struct mei_cl_device *device)
if (device->event_cb)
mei_cl_read_start(device->cl, 0, NULL);
if (!device->ops || !device->ops->enable)
return 0;
return device->ops->enable(device);
return 0;
}
EXPORT_SYMBOL_GPL(mei_cl_enable_device);
@ -467,9 +514,6 @@ int mei_cl_disable_device(struct mei_cl_device *device)
dev = cl->dev;
if (device->ops && device->ops->disable)
device->ops->disable(device);
device->event_cb = NULL;
mutex_lock(&dev->device_lock);
@ -480,8 +524,6 @@ int mei_cl_disable_device(struct mei_cl_device *device)
goto out;
}
cl->state = MEI_FILE_DISCONNECTING;
err = mei_cl_disconnect(cl);
if (err < 0) {
dev_err(dev->dev, "Could not disconnect from the ME client");

View File

@ -83,7 +83,7 @@ void mei_me_cl_put(struct mei_me_client *me_cl)
}
/**
* __mei_me_cl_del - delete me client form the list and decrease
* __mei_me_cl_del - delete me client from the list and decrease
* reference counter
*
* @dev: mei device
@ -96,10 +96,24 @@ static void __mei_me_cl_del(struct mei_device *dev, struct mei_me_client *me_cl)
if (!me_cl)
return;
list_del(&me_cl->list);
list_del_init(&me_cl->list);
mei_me_cl_put(me_cl);
}
/**
* mei_me_cl_del - delete me client from the list and decrease
* reference counter
*
* @dev: mei device
* @me_cl: me client
*/
void mei_me_cl_del(struct mei_device *dev, struct mei_me_client *me_cl)
{
down_write(&dev->me_clients_rwsem);
__mei_me_cl_del(dev, me_cl);
up_write(&dev->me_clients_rwsem);
}
/**
* mei_me_cl_add - add me client to the list
*
@ -317,7 +331,7 @@ static inline bool mei_cl_cmp_id(const struct mei_cl *cl1,
{
return cl1 && cl2 &&
(cl1->host_client_id == cl2->host_client_id) &&
(cl1->me_client_id == cl2->me_client_id);
(mei_cl_me_id(cl1) == mei_cl_me_id(cl2));
}
/**
@ -546,6 +560,7 @@ void mei_cl_init(struct mei_cl *cl, struct mei_device *dev)
INIT_LIST_HEAD(&cl->link);
INIT_LIST_HEAD(&cl->device_link);
cl->writing_state = MEI_IDLE;
cl->state = MEI_FILE_INITIALIZING;
cl->dev = dev;
}
@ -619,7 +634,7 @@ int mei_cl_link(struct mei_cl *cl, int id)
}
/**
* mei_cl_unlink - remove me_cl from the list
* mei_cl_unlink - remove host client from the list
*
* @cl: host client
*
@ -667,17 +682,17 @@ void mei_host_client_init(struct work_struct *work)
me_cl = mei_me_cl_by_uuid(dev, &mei_amthif_guid);
if (me_cl)
mei_amthif_host_init(dev);
mei_amthif_host_init(dev, me_cl);
mei_me_cl_put(me_cl);
me_cl = mei_me_cl_by_uuid(dev, &mei_wd_guid);
if (me_cl)
mei_wd_host_init(dev);
mei_wd_host_init(dev, me_cl);
mei_me_cl_put(me_cl);
me_cl = mei_me_cl_by_uuid(dev, &mei_nfc_guid);
if (me_cl)
mei_nfc_host_init(dev);
mei_nfc_host_init(dev, me_cl);
mei_me_cl_put(me_cl);
@ -699,7 +714,7 @@ void mei_host_client_init(struct work_struct *work)
bool mei_hbuf_acquire(struct mei_device *dev)
{
if (mei_pg_state(dev) == MEI_PG_ON ||
dev->pg_event == MEI_PG_EVENT_WAIT) {
mei_pg_in_transition(dev)) {
dev_dbg(dev->dev, "device is in pg\n");
return false;
}
@ -714,6 +729,120 @@ bool mei_hbuf_acquire(struct mei_device *dev)
return true;
}
/**
* mei_cl_set_disconnected - set disconnected state and clear
* associated states and resources
*
* @cl: host client
*/
void mei_cl_set_disconnected(struct mei_cl *cl)
{
struct mei_device *dev = cl->dev;
if (cl->state == MEI_FILE_DISCONNECTED ||
cl->state == MEI_FILE_INITIALIZING)
return;
cl->state = MEI_FILE_DISCONNECTED;
mei_io_list_flush(&dev->ctrl_rd_list, cl);
mei_io_list_flush(&dev->ctrl_wr_list, cl);
cl->mei_flow_ctrl_creds = 0;
cl->timer_count = 0;
if (!cl->me_cl)
return;
if (!WARN_ON(cl->me_cl->connect_count == 0))
cl->me_cl->connect_count--;
if (cl->me_cl->connect_count == 0)
cl->me_cl->mei_flow_ctrl_creds = 0;
mei_me_cl_put(cl->me_cl);
cl->me_cl = NULL;
}
static int mei_cl_set_connecting(struct mei_cl *cl, struct mei_me_client *me_cl)
{
if (!mei_me_cl_get(me_cl))
return -ENOENT;
/* only one connection is allowed for fixed address clients */
if (me_cl->props.fixed_address) {
if (me_cl->connect_count) {
mei_me_cl_put(me_cl);
return -EBUSY;
}
}
cl->me_cl = me_cl;
cl->state = MEI_FILE_CONNECTING;
cl->me_cl->connect_count++;
return 0;
}
/*
* mei_cl_send_disconnect - send disconnect request
*
* @cl: host client
* @cb: callback block
*
* Return: 0, OK; otherwise, error.
*/
static int mei_cl_send_disconnect(struct mei_cl *cl, struct mei_cl_cb *cb)
{
struct mei_device *dev;
int ret;
dev = cl->dev;
ret = mei_hbm_cl_disconnect_req(dev, cl);
cl->status = ret;
if (ret) {
cl->state = MEI_FILE_DISCONNECT_REPLY;
return ret;
}
list_move_tail(&cb->list, &dev->ctrl_rd_list.list);
cl->timer_count = MEI_CONNECT_TIMEOUT;
return 0;
}
/**
* mei_cl_irq_disconnect - processes close related operation from
* interrupt thread context - send disconnect request
*
* @cl: client
* @cb: callback block.
* @cmpl_list: complete list.
*
* Return: 0, OK; otherwise, error.
*/
int mei_cl_irq_disconnect(struct mei_cl *cl, struct mei_cl_cb *cb,
struct mei_cl_cb *cmpl_list)
{
struct mei_device *dev = cl->dev;
u32 msg_slots;
int slots;
int ret;
msg_slots = mei_data2slots(sizeof(struct hbm_client_connect_request));
slots = mei_hbuf_empty_slots(dev);
if (slots < msg_slots)
return -EMSGSIZE;
ret = mei_cl_send_disconnect(cl, cb);
if (ret)
list_move_tail(&cb->list, &cmpl_list->list);
return ret;
}
/**
* mei_cl_disconnect - disconnect host client from the me one
*
@ -736,9 +865,14 @@ int mei_cl_disconnect(struct mei_cl *cl)
cl_dbg(dev, cl, "disconnecting");
if (cl->state != MEI_FILE_DISCONNECTING)
if (!mei_cl_is_connected(cl))
return 0;
if (mei_cl_is_fixed_address(cl)) {
mei_cl_set_disconnected(cl);
return 0;
}
rets = pm_runtime_get(dev->dev);
if (rets < 0 && rets != -EINPROGRESS) {
pm_runtime_put_noidle(dev->dev);
@ -746,44 +880,41 @@ int mei_cl_disconnect(struct mei_cl *cl)
return rets;
}
cl->state = MEI_FILE_DISCONNECTING;
cb = mei_io_cb_init(cl, MEI_FOP_DISCONNECT, NULL);
rets = cb ? 0 : -ENOMEM;
if (rets)
goto free;
goto out;
cl_dbg(dev, cl, "add disconnect cb to control write list\n");
list_add_tail(&cb->list, &dev->ctrl_wr_list.list);
if (mei_hbuf_acquire(dev)) {
if (mei_hbm_cl_disconnect_req(dev, cl)) {
rets = -ENODEV;
rets = mei_cl_send_disconnect(cl, cb);
if (rets) {
cl_err(dev, cl, "failed to disconnect.\n");
goto free;
goto out;
}
cl->timer_count = MEI_CONNECT_TIMEOUT;
mdelay(10); /* Wait for hardware disconnection ready */
list_add_tail(&cb->list, &dev->ctrl_rd_list.list);
} else {
cl_dbg(dev, cl, "add disconnect cb to control write list\n");
list_add_tail(&cb->list, &dev->ctrl_wr_list.list);
}
mutex_unlock(&dev->device_lock);
wait_event_timeout(cl->wait,
MEI_FILE_DISCONNECTED == cl->state,
mei_secs_to_jiffies(MEI_CL_CONNECT_TIMEOUT));
wait_event_timeout(cl->wait, cl->state == MEI_FILE_DISCONNECT_REPLY,
mei_secs_to_jiffies(MEI_CL_CONNECT_TIMEOUT));
mutex_lock(&dev->device_lock);
if (MEI_FILE_DISCONNECTED == cl->state) {
rets = 0;
cl_dbg(dev, cl, "successfully disconnected from FW client.\n");
} else {
rets = cl->status;
if (cl->state != MEI_FILE_DISCONNECT_REPLY) {
cl_dbg(dev, cl, "timeout on disconnect from FW client.\n");
rets = -ETIME;
}
mei_io_list_flush(&dev->ctrl_rd_list, cl);
mei_io_list_flush(&dev->ctrl_wr_list, cl);
free:
out:
/* we disconnect also on error */
mei_cl_set_disconnected(cl);
if (!rets)
cl_dbg(dev, cl, "successfully disconnected from FW client.\n");
cl_dbg(dev, cl, "rpm: autosuspend\n");
pm_runtime_mark_last_busy(dev->dev);
pm_runtime_put_autosuspend(dev->dev);
@ -801,53 +932,119 @@ free:
*
* Return: true if other client is connected, false - otherwise.
*/
bool mei_cl_is_other_connecting(struct mei_cl *cl)
static bool mei_cl_is_other_connecting(struct mei_cl *cl)
{
struct mei_device *dev;
struct mei_cl *ocl; /* the other client */
if (WARN_ON(!cl || !cl->dev))
return false;
struct mei_cl_cb *cb;
dev = cl->dev;
list_for_each_entry(ocl, &dev->file_list, link) {
if (ocl->state == MEI_FILE_CONNECTING &&
ocl != cl &&
cl->me_client_id == ocl->me_client_id)
list_for_each_entry(cb, &dev->ctrl_rd_list.list, list) {
if (cb->fop_type == MEI_FOP_CONNECT &&
mei_cl_me_id(cl) == mei_cl_me_id(cb->cl))
return true;
}
return false;
}
/**
* mei_cl_send_connect - send connect request
*
* @cl: host client
* @cb: callback block
*
* Return: 0, OK; otherwise, error.
*/
static int mei_cl_send_connect(struct mei_cl *cl, struct mei_cl_cb *cb)
{
struct mei_device *dev;
int ret;
dev = cl->dev;
ret = mei_hbm_cl_connect_req(dev, cl);
cl->status = ret;
if (ret) {
cl->state = MEI_FILE_DISCONNECT_REPLY;
return ret;
}
list_move_tail(&cb->list, &dev->ctrl_rd_list.list);
cl->timer_count = MEI_CONNECT_TIMEOUT;
return 0;
}
/**
* mei_cl_irq_connect - send connect request in irq_thread context
*
* @cl: host client
* @cb: callback block
* @cmpl_list: complete list
*
* Return: 0, OK; otherwise, error.
*/
int mei_cl_irq_connect(struct mei_cl *cl, struct mei_cl_cb *cb,
struct mei_cl_cb *cmpl_list)
{
struct mei_device *dev = cl->dev;
u32 msg_slots;
int slots;
int rets;
msg_slots = mei_data2slots(sizeof(struct hbm_client_connect_request));
slots = mei_hbuf_empty_slots(dev);
if (mei_cl_is_other_connecting(cl))
return 0;
if (slots < msg_slots)
return -EMSGSIZE;
rets = mei_cl_send_connect(cl, cb);
if (rets)
list_move_tail(&cb->list, &cmpl_list->list);
return rets;
}
/**
* mei_cl_connect - connect host client to the me one
*
* @cl: host client
* @me_cl: me client
* @file: pointer to file structure
*
* Locking: called under "dev->device_lock" lock
*
* Return: 0 on success, <0 on failure.
*/
int mei_cl_connect(struct mei_cl *cl, struct file *file)
int mei_cl_connect(struct mei_cl *cl, struct mei_me_client *me_cl,
struct file *file)
{
struct mei_device *dev;
struct mei_cl_cb *cb;
int rets;
if (WARN_ON(!cl || !cl->dev))
if (WARN_ON(!cl || !cl->dev || !me_cl))
return -ENODEV;
dev = cl->dev;
rets = mei_cl_set_connecting(cl, me_cl);
if (rets)
return rets;
if (mei_cl_is_fixed_address(cl)) {
cl->state = MEI_FILE_CONNECTED;
return 0;
}
rets = pm_runtime_get(dev->dev);
if (rets < 0 && rets != -EINPROGRESS) {
pm_runtime_put_noidle(dev->dev);
cl_err(dev, cl, "rpm: get failed %d\n", rets);
return rets;
goto nortpm;
}
cb = mei_io_cb_init(cl, MEI_FOP_CONNECT, file);
@ -855,45 +1052,40 @@ int mei_cl_connect(struct mei_cl *cl, struct file *file)
if (rets)
goto out;
list_add_tail(&cb->list, &dev->ctrl_wr_list.list);
/* run hbuf acquire last so we don't have to undo */
if (!mei_cl_is_other_connecting(cl) && mei_hbuf_acquire(dev)) {
cl->state = MEI_FILE_CONNECTING;
if (mei_hbm_cl_connect_req(dev, cl)) {
rets = -ENODEV;
rets = mei_cl_send_connect(cl, cb);
if (rets)
goto out;
}
cl->timer_count = MEI_CONNECT_TIMEOUT;
list_add_tail(&cb->list, &dev->ctrl_rd_list.list);
} else {
cl->state = MEI_FILE_INITIALIZING;
list_add_tail(&cb->list, &dev->ctrl_wr_list.list);
}
mutex_unlock(&dev->device_lock);
wait_event_timeout(cl->wait,
(cl->state == MEI_FILE_CONNECTED ||
cl->state == MEI_FILE_DISCONNECTED),
cl->state == MEI_FILE_DISCONNECT_REPLY),
mei_secs_to_jiffies(MEI_CL_CONNECT_TIMEOUT));
mutex_lock(&dev->device_lock);
if (!mei_cl_is_connected(cl)) {
cl->state = MEI_FILE_DISCONNECTED;
/* something went really wrong */
/* timeout or something went really wrong */
if (!cl->status)
cl->status = -EFAULT;
mei_io_list_flush(&dev->ctrl_rd_list, cl);
mei_io_list_flush(&dev->ctrl_wr_list, cl);
}
rets = cl->status;
out:
cl_dbg(dev, cl, "rpm: autosuspend\n");
pm_runtime_mark_last_busy(dev->dev);
pm_runtime_put_autosuspend(dev->dev);
mei_io_cb_free(cb);
nortpm:
if (!mei_cl_is_connected(cl))
mei_cl_set_disconnected(cl);
return rets;
}
@ -934,36 +1126,29 @@ err:
* @cl: private data of the file object
*
* Return: 1 if mei_flow_ctrl_creds >0, 0 - otherwise.
* -ENOENT if mei_cl is not present
* -EINVAL if single_recv_buf == 0
*/
int mei_cl_flow_ctrl_creds(struct mei_cl *cl)
{
struct mei_device *dev;
struct mei_me_client *me_cl;
int rets = 0;
int rets;
if (WARN_ON(!cl || !cl->dev))
if (WARN_ON(!cl || !cl->me_cl))
return -EINVAL;
dev = cl->dev;
if (cl->mei_flow_ctrl_creds > 0)
return 1;
me_cl = mei_me_cl_by_uuid_id(dev, &cl->cl_uuid, cl->me_client_id);
if (!me_cl) {
cl_err(dev, cl, "no such me client %d\n", cl->me_client_id);
return -ENOENT;
if (mei_cl_is_fixed_address(cl)) {
rets = mei_cl_read_start(cl, mei_cl_mtu(cl), NULL);
if (rets && rets != -EBUSY)
return rets;
return 1;
}
if (me_cl->mei_flow_ctrl_creds > 0) {
rets = 1;
if (WARN_ON(me_cl->props.single_recv_buf == 0))
rets = -EINVAL;
if (mei_cl_is_single_recv_buf(cl)) {
if (cl->me_cl->mei_flow_ctrl_creds > 0)
return 1;
}
mei_me_cl_put(me_cl);
return rets;
return 0;
}
/**
@ -973,43 +1158,26 @@ int mei_cl_flow_ctrl_creds(struct mei_cl *cl)
*
* Return:
* 0 on success
* -ENOENT when me client is not found
* -EINVAL when ctrl credits are <= 0
*/
int mei_cl_flow_ctrl_reduce(struct mei_cl *cl)
{
struct mei_device *dev;
struct mei_me_client *me_cl;
int rets;
if (WARN_ON(!cl || !cl->dev))
if (WARN_ON(!cl || !cl->me_cl))
return -EINVAL;
dev = cl->dev;
if (mei_cl_is_fixed_address(cl))
return 0;
me_cl = mei_me_cl_by_uuid_id(dev, &cl->cl_uuid, cl->me_client_id);
if (!me_cl) {
cl_err(dev, cl, "no such me client %d\n", cl->me_client_id);
return -ENOENT;
}
if (me_cl->props.single_recv_buf) {
if (WARN_ON(me_cl->mei_flow_ctrl_creds <= 0)) {
rets = -EINVAL;
goto out;
}
me_cl->mei_flow_ctrl_creds--;
if (mei_cl_is_single_recv_buf(cl)) {
if (WARN_ON(cl->me_cl->mei_flow_ctrl_creds <= 0))
return -EINVAL;
cl->me_cl->mei_flow_ctrl_creds--;
} else {
if (WARN_ON(cl->mei_flow_ctrl_creds <= 0)) {
rets = -EINVAL;
goto out;
}
if (WARN_ON(cl->mei_flow_ctrl_creds <= 0))
return -EINVAL;
cl->mei_flow_ctrl_creds--;
}
rets = 0;
out:
mei_me_cl_put(me_cl);
return rets;
return 0;
}
/**
@ -1025,7 +1193,6 @@ int mei_cl_read_start(struct mei_cl *cl, size_t length, struct file *fp)
{
struct mei_device *dev;
struct mei_cl_cb *cb;
struct mei_me_client *me_cl;
int rets;
if (WARN_ON(!cl || !cl->dev))
@ -1040,27 +1207,29 @@ int mei_cl_read_start(struct mei_cl *cl, size_t length, struct file *fp)
if (!list_empty(&cl->rd_pending))
return -EBUSY;
me_cl = mei_me_cl_by_uuid_id(dev, &cl->cl_uuid, cl->me_client_id);
if (!me_cl) {
cl_err(dev, cl, "no such me client %d\n", cl->me_client_id);
if (!mei_me_cl_is_active(cl->me_cl)) {
cl_err(dev, cl, "no such me client\n");
return -ENOTTY;
}
/* always allocate at least client max message */
length = max_t(size_t, length, me_cl->props.max_msg_length);
mei_me_cl_put(me_cl);
length = max_t(size_t, length, mei_cl_mtu(cl));
cb = mei_cl_alloc_cb(cl, length, MEI_FOP_READ, fp);
if (!cb)
return -ENOMEM;
if (mei_cl_is_fixed_address(cl)) {
list_add_tail(&cb->list, &cl->rd_pending);
return 0;
}
rets = pm_runtime_get(dev->dev);
if (rets < 0 && rets != -EINPROGRESS) {
pm_runtime_put_noidle(dev->dev);
cl_err(dev, cl, "rpm: get failed %d\n", rets);
return rets;
goto nortpm;
}
cb = mei_cl_alloc_cb(cl, length, MEI_FOP_READ, fp);
rets = cb ? 0 : -ENOMEM;
if (rets)
goto out;
if (mei_hbuf_acquire(dev)) {
rets = mei_hbm_cl_flow_control_req(dev, cl);
if (rets < 0)
@ -1068,6 +1237,7 @@ int mei_cl_read_start(struct mei_cl *cl, size_t length, struct file *fp)
list_add_tail(&cb->list, &cl->rd_pending);
} else {
rets = 0;
list_add_tail(&cb->list, &dev->ctrl_wr_list.list);
}
@ -1075,7 +1245,7 @@ out:
cl_dbg(dev, cl, "rpm: autosuspend\n");
pm_runtime_mark_last_busy(dev->dev);
pm_runtime_put_autosuspend(dev->dev);
nortpm:
if (rets)
mei_io_cb_free(cb);
@ -1102,6 +1272,7 @@ int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb,
u32 msg_slots;
int slots;
int rets;
bool first_chunk;
if (WARN_ON(!cl || !cl->dev))
return -ENODEV;
@ -1110,7 +1281,9 @@ int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb,
buf = &cb->buf;
rets = mei_cl_flow_ctrl_creds(cl);
first_chunk = cb->buf_idx == 0;
rets = first_chunk ? mei_cl_flow_ctrl_creds(cl) : 1;
if (rets < 0)
return rets;
@ -1123,8 +1296,8 @@ int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb,
len = buf->size - cb->buf_idx;
msg_slots = mei_data2slots(len);
mei_hdr.host_addr = cl->host_client_id;
mei_hdr.me_addr = cl->me_client_id;
mei_hdr.host_addr = mei_cl_host_addr(cl);
mei_hdr.me_addr = mei_cl_me_id(cl);
mei_hdr.reserved = 0;
mei_hdr.internal = cb->internal;
@ -1157,12 +1330,14 @@ int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb,
cb->buf_idx += mei_hdr.length;
cb->completed = mei_hdr.msg_complete == 1;
if (mei_hdr.msg_complete) {
if (first_chunk) {
if (mei_cl_flow_ctrl_reduce(cl))
return -EIO;
list_move_tail(&cb->list, &dev->write_waiting_list.list);
}
if (mei_hdr.msg_complete)
list_move_tail(&cb->list, &dev->write_waiting_list.list);
return 0;
}
@ -1207,8 +1382,8 @@ int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb, bool blocking)
cb->buf_idx = 0;
cl->writing_state = MEI_IDLE;
mei_hdr.host_addr = cl->host_client_id;
mei_hdr.me_addr = cl->me_client_id;
mei_hdr.host_addr = mei_cl_host_addr(cl);
mei_hdr.me_addr = mei_cl_me_id(cl);
mei_hdr.reserved = 0;
mei_hdr.msg_complete = 0;
mei_hdr.internal = cb->internal;
@ -1241,21 +1416,19 @@ int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb, bool blocking)
if (rets)
goto err;
rets = mei_cl_flow_ctrl_reduce(cl);
if (rets)
goto err;
cl->writing_state = MEI_WRITING;
cb->buf_idx = mei_hdr.length;
cb->completed = mei_hdr.msg_complete == 1;
out:
if (mei_hdr.msg_complete) {
rets = mei_cl_flow_ctrl_reduce(cl);
if (rets < 0)
goto err;
if (mei_hdr.msg_complete)
list_add_tail(&cb->list, &dev->write_waiting_list.list);
} else {
else
list_add_tail(&cb->list, &dev->write_list.list);
}
if (blocking && cl->writing_state != MEI_WRITE_COMPLETE) {
@ -1289,20 +1462,36 @@ err:
*/
void mei_cl_complete(struct mei_cl *cl, struct mei_cl_cb *cb)
{
if (cb->fop_type == MEI_FOP_WRITE) {
mei_io_cb_free(cb);
cb = NULL;
cl->writing_state = MEI_WRITE_COMPLETE;
if (waitqueue_active(&cl->tx_wait))
wake_up_interruptible(&cl->tx_wait);
struct mei_device *dev = cl->dev;
} else if (cb->fop_type == MEI_FOP_READ) {
switch (cb->fop_type) {
case MEI_FOP_WRITE:
mei_io_cb_free(cb);
cl->writing_state = MEI_WRITE_COMPLETE;
if (waitqueue_active(&cl->tx_wait)) {
wake_up_interruptible(&cl->tx_wait);
} else {
pm_runtime_mark_last_busy(dev->dev);
pm_request_autosuspend(dev->dev);
}
break;
case MEI_FOP_READ:
list_add_tail(&cb->list, &cl->rd_completed);
if (waitqueue_active(&cl->rx_wait))
wake_up_interruptible_all(&cl->rx_wait);
else
mei_cl_bus_rx_event(cl);
break;
case MEI_FOP_CONNECT:
case MEI_FOP_DISCONNECT:
if (waitqueue_active(&cl->wait))
wake_up(&cl->wait);
break;
default:
BUG_ON(0);
}
}
@ -1312,16 +1501,12 @@ void mei_cl_complete(struct mei_cl *cl, struct mei_cl_cb *cb)
*
* @dev: mei device
*/
void mei_cl_all_disconnect(struct mei_device *dev)
{
struct mei_cl *cl;
list_for_each_entry(cl, &dev->file_list, link) {
cl->state = MEI_FILE_DISCONNECTED;
cl->mei_flow_ctrl_creds = 0;
cl->timer_count = 0;
}
list_for_each_entry(cl, &dev->file_list, link)
mei_cl_set_disconnected(cl);
}

View File

@ -44,6 +44,30 @@ void mei_me_cl_rm_by_uuid_id(struct mei_device *dev,
const uuid_le *uuid, u8 id);
void mei_me_cl_rm_all(struct mei_device *dev);
/**
* mei_me_cl_is_active - check whether me client is active in the fw
*
* @me_cl: me client
*
* Return: true if the me client is active in the firmware
*/
static inline bool mei_me_cl_is_active(const struct mei_me_client *me_cl)
{
return !list_empty_careful(&me_cl->list);
}
/**
* mei_me_cl_uuid - return me client protocol name (uuid)
*
* @me_cl: me client
*
* Return: me client protocol name
*/
static inline const uuid_le *mei_me_cl_uuid(const struct mei_me_client *me_cl)
{
return &me_cl->props.protocol_name;
}
/*
* MEI IO Functions
*/
@ -94,18 +118,96 @@ int mei_cl_flow_ctrl_reduce(struct mei_cl *cl);
/**
* mei_cl_is_connected - host client is connected
*
* @cl: host clinet
* @cl: host client
*
* Return: true if the host clinet is connected
* Return: true if the host client is connected
*/
static inline bool mei_cl_is_connected(struct mei_cl *cl)
{
return cl->state == MEI_FILE_CONNECTED;
}
bool mei_cl_is_other_connecting(struct mei_cl *cl);
/**
* mei_cl_me_id - me client id
*
* @cl: host client
*
* Return: me client id or 0 if client is not connected
*/
static inline u8 mei_cl_me_id(const struct mei_cl *cl)
{
return cl->me_cl ? cl->me_cl->client_id : 0;
}
/**
* mei_cl_mtu - maximal message that client can send and receive
*
* @cl: host client
*
* Return: mtu
*/
static inline size_t mei_cl_mtu(const struct mei_cl *cl)
{
return cl->me_cl->props.max_msg_length;
}
/**
* mei_cl_is_fixed_address - check whether the me client uses fixed address
*
* @cl: host client
*
* Return: true if the client is connected and it has fixed me address
*/
static inline bool mei_cl_is_fixed_address(const struct mei_cl *cl)
{
return cl->me_cl && cl->me_cl->props.fixed_address;
}
/**
* mei_cl_is_single_recv_buf- check whether the me client
* uses single receiving buffer
*
* @cl: host client
*
* Return: true if single_recv_buf == 1; 0 otherwise
*/
static inline bool mei_cl_is_single_recv_buf(const struct mei_cl *cl)
{
return cl->me_cl->props.single_recv_buf;
}
/**
* mei_cl_uuid - client's uuid
*
* @cl: host client
*
* Return: return uuid of connected me client
*/
static inline const uuid_le *mei_cl_uuid(const struct mei_cl *cl)
{
return mei_me_cl_uuid(cl->me_cl);
}
/**
* mei_cl_host_addr - client's host address
*
* @cl: host client
*
* Return: 0 for fixed address client, host address for dynamic client
*/
static inline u8 mei_cl_host_addr(const struct mei_cl *cl)
{
return mei_cl_is_fixed_address(cl) ? 0 : cl->host_client_id;
}
int mei_cl_disconnect(struct mei_cl *cl);
int mei_cl_connect(struct mei_cl *cl, struct file *file);
void mei_cl_set_disconnected(struct mei_cl *cl);
int mei_cl_irq_disconnect(struct mei_cl *cl, struct mei_cl_cb *cb,
struct mei_cl_cb *cmpl_list);
int mei_cl_connect(struct mei_cl *cl, struct mei_me_client *me_cl,
struct file *file);
int mei_cl_irq_connect(struct mei_cl *cl, struct mei_cl_cb *cb,
struct mei_cl_cb *cmpl_list);
int mei_cl_read_start(struct mei_cl *cl, size_t length, struct file *fp);
int mei_cl_irq_read_msg(struct mei_cl *cl, struct mei_msg_hdr *hdr,
struct mei_cl_cb *cmpl_list);
@ -117,14 +219,12 @@ void mei_cl_complete(struct mei_cl *cl, struct mei_cl_cb *cb);
void mei_host_client_init(struct work_struct *work);
void mei_cl_all_disconnect(struct mei_device *dev);
void mei_cl_all_wakeup(struct mei_device *dev);
void mei_cl_all_write_clear(struct mei_device *dev);
#define MEI_CL_FMT "cl:host=%02d me=%02d "
#define MEI_CL_PRM(cl) (cl)->host_client_id, (cl)->me_client_id
#define MEI_CL_PRM(cl) (cl)->host_client_id, mei_cl_me_id(cl)
#define cl_dbg(dev, cl, format, arg...) \
dev_dbg((dev)->dev, MEI_CL_FMT format, MEI_CL_PRM(cl), ##arg)

View File

@ -116,7 +116,7 @@ static ssize_t mei_dbgfs_read_active(struct file *fp, char __user *ubuf,
pos += scnprintf(buf + pos, bufsz - pos,
"%2d|%2d|%4d|%5d|%2d|%2d|\n",
i, cl->me_client_id, cl->host_client_id, cl->state,
i, mei_cl_me_id(cl), cl->host_client_id, cl->state,
!list_empty(&cl->rd_completed), cl->writing_state);
i++;
}
@ -149,6 +149,13 @@ static ssize_t mei_dbgfs_read_devstate(struct file *fp, char __user *ubuf,
mei_dev_state_str(dev->dev_state));
pos += scnprintf(buf + pos, bufsz - pos, "hbm: %s\n",
mei_hbm_state_str(dev->hbm_state));
if (dev->hbm_state == MEI_HBM_STARTED) {
pos += scnprintf(buf + pos, bufsz - pos, "hbm features:\n");
pos += scnprintf(buf + pos, bufsz - pos, "\tPG: %01d\n",
dev->hbm_f_pg_supported);
}
pos += scnprintf(buf + pos, bufsz - pos, "pg: %s, %s\n",
mei_pg_is_enabled(dev) ? "ENABLED" : "DISABLED",
mei_pg_state_str(mei_pg_state(dev)));
@ -209,6 +216,12 @@ int mei_dbgfs_register(struct mei_device *dev, const char *name)
dev_err(dev->dev, "devstate: registration failed\n");
goto err;
}
f = debugfs_create_bool("allow_fixed_address", S_IRUSR | S_IWUSR, dir,
&dev->allow_fixed_address);
if (!f) {
dev_err(dev->dev, "allow_fixed_address: registration failed\n");
goto err;
}
dev->dbgfs_dir = dir;
return 0;
err:

View File

@ -150,8 +150,8 @@ void mei_hbm_cl_hdr(struct mei_cl *cl, u8 hbm_cmd, void *buf, size_t len)
memset(cmd, 0, len);
cmd->hbm_cmd = hbm_cmd;
cmd->host_addr = cl->host_client_id;
cmd->me_addr = cl->me_client_id;
cmd->host_addr = mei_cl_host_addr(cl);
cmd->me_addr = mei_cl_me_id(cl);
}
/**
@ -188,8 +188,8 @@ int mei_hbm_cl_write(struct mei_device *dev,
static inline
bool mei_hbm_cl_addr_equal(struct mei_cl *cl, struct mei_hbm_cl_cmd *cmd)
{
return cl->host_client_id == cmd->host_addr &&
cl->me_client_id == cmd->me_addr;
return mei_cl_host_addr(cl) == cmd->host_addr &&
mei_cl_me_id(cl) == cmd->me_addr;
}
/**
@ -572,7 +572,7 @@ static void mei_hbm_cl_disconnect_res(struct mei_device *dev, struct mei_cl *cl,
cl_dbg(dev, cl, "hbm: disconnect response status=%d\n", rs->status);
if (rs->status == MEI_CL_DISCONN_SUCCESS)
cl->state = MEI_FILE_DISCONNECTED;
cl->state = MEI_FILE_DISCONNECT_REPLY;
cl->status = 0;
}
@ -611,7 +611,7 @@ static void mei_hbm_cl_connect_res(struct mei_device *dev, struct mei_cl *cl,
if (rs->status == MEI_CL_CONN_SUCCESS)
cl->state = MEI_FILE_CONNECTED;
else
cl->state = MEI_FILE_DISCONNECTED;
cl->state = MEI_FILE_DISCONNECT_REPLY;
cl->status = mei_cl_conn_status_to_errno(rs->status);
}
@ -680,8 +680,8 @@ static int mei_hbm_fw_disconnect_req(struct mei_device *dev,
cl = mei_hbm_cl_find_by_cmd(dev, disconnect_req);
if (cl) {
cl_dbg(dev, cl, "disconnect request received\n");
cl->state = MEI_FILE_DISCONNECTED;
cl_dbg(dev, cl, "fw disconnect request received\n");
cl->state = MEI_FILE_DISCONNECTING;
cl->timer_count = 0;
cb = mei_io_cb_init(cl, MEI_FOP_DISCONNECT_RSP, NULL);

View File

@ -663,17 +663,46 @@ int mei_me_pg_exit_sync(struct mei_device *dev)
mutex_lock(&dev->device_lock);
reply:
if (dev->pg_event == MEI_PG_EVENT_RECEIVED)
ret = mei_hbm_pg(dev, MEI_PG_ISOLATION_EXIT_RES_CMD);
if (dev->pg_event != MEI_PG_EVENT_RECEIVED) {
ret = -ETIME;
goto out;
}
dev->pg_event = MEI_PG_EVENT_INTR_WAIT;
ret = mei_hbm_pg(dev, MEI_PG_ISOLATION_EXIT_RES_CMD);
if (ret)
return ret;
mutex_unlock(&dev->device_lock);
wait_event_timeout(dev->wait_pg,
dev->pg_event == MEI_PG_EVENT_INTR_RECEIVED, timeout);
mutex_lock(&dev->device_lock);
if (dev->pg_event == MEI_PG_EVENT_INTR_RECEIVED)
ret = 0;
else
ret = -ETIME;
out:
dev->pg_event = MEI_PG_EVENT_IDLE;
hw->pg_state = MEI_PG_OFF;
return ret;
}
/**
* mei_me_pg_in_transition - is device now in pg transition
*
* @dev: the device structure
*
* Return: true if in pg transition, false otherwise
*/
static bool mei_me_pg_in_transition(struct mei_device *dev)
{
return dev->pg_event >= MEI_PG_EVENT_WAIT &&
dev->pg_event <= MEI_PG_EVENT_INTR_WAIT;
}
/**
* mei_me_pg_is_enabled - detect if PG is supported by HW
*
@ -704,6 +733,24 @@ notsupported:
return false;
}
/**
* mei_me_pg_intr - perform pg processing in interrupt thread handler
*
* @dev: the device structure
*/
static void mei_me_pg_intr(struct mei_device *dev)
{
struct mei_me_hw *hw = to_me_hw(dev);
if (dev->pg_event != MEI_PG_EVENT_INTR_WAIT)
return;
dev->pg_event = MEI_PG_EVENT_INTR_RECEIVED;
hw->pg_state = MEI_PG_OFF;
if (waitqueue_active(&dev->wait_pg))
wake_up(&dev->wait_pg);
}
/**
* mei_me_irq_quick_handler - The ISR of the MEI device
*
@ -761,6 +808,8 @@ irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id)
goto end;
}
mei_me_pg_intr(dev);
/* check if we need to start the dev */
if (!mei_host_is_ready(dev)) {
if (mei_hw_is_ready(dev)) {
@ -797,9 +846,10 @@ irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id)
/*
* During PG handshake only allowed write is the replay to the
* PG exit message, so block calling write function
* if the pg state is not idle
* if the pg event is in PG handshake
*/
if (dev->pg_event == MEI_PG_EVENT_IDLE) {
if (dev->pg_event != MEI_PG_EVENT_WAIT &&
dev->pg_event != MEI_PG_EVENT_RECEIVED) {
rets = mei_irq_write_handler(dev, &complete_list);
dev->hbuf_is_ready = mei_hbuf_is_ready(dev);
}
@ -824,6 +874,7 @@ static const struct mei_hw_ops mei_me_hw_ops = {
.hw_config = mei_me_hw_config,
.hw_start = mei_me_hw_start,
.pg_in_transition = mei_me_pg_in_transition,
.pg_is_enabled = mei_me_pg_is_enabled,
.intr_clear = mei_me_intr_clear,

View File

@ -16,6 +16,7 @@
#include <linux/pci.h>
#include <linux/jiffies.h>
#include <linux/ktime.h>
#include <linux/delay.h>
#include <linux/kthread.h>
#include <linux/irqreturn.h>
@ -218,26 +219,25 @@ static u32 mei_txe_aliveness_get(struct mei_device *dev)
*
* Polls for HICR_HOST_ALIVENESS_RESP.ALIVENESS_RESP to be set
*
* Return: > 0 if the expected value was received, -ETIME otherwise
* Return: 0 if the expected value was received, -ETIME otherwise
*/
static int mei_txe_aliveness_poll(struct mei_device *dev, u32 expected)
{
struct mei_txe_hw *hw = to_txe_hw(dev);
int t = 0;
ktime_t stop, start;
start = ktime_get();
stop = ktime_add(start, ms_to_ktime(SEC_ALIVENESS_WAIT_TIMEOUT));
do {
hw->aliveness = mei_txe_aliveness_get(dev);
if (hw->aliveness == expected) {
dev->pg_event = MEI_PG_EVENT_IDLE;
dev_dbg(dev->dev,
"aliveness settled after %d msecs\n", t);
return t;
dev_dbg(dev->dev, "aliveness settled after %lld usecs\n",
ktime_to_us(ktime_sub(ktime_get(), start)));
return 0;
}
mutex_unlock(&dev->device_lock);
msleep(MSEC_PER_SEC / 5);
mutex_lock(&dev->device_lock);
t += MSEC_PER_SEC / 5;
} while (t < SEC_ALIVENESS_WAIT_TIMEOUT);
usleep_range(20, 50);
} while (ktime_before(ktime_get(), stop));
dev->pg_event = MEI_PG_EVENT_IDLE;
dev_err(dev->dev, "aliveness timed out\n");
@ -301,6 +301,18 @@ int mei_txe_aliveness_set_sync(struct mei_device *dev, u32 req)
return 0;
}
/**
* mei_txe_pg_in_transition - is device now in pg transition
*
* @dev: the device structure
*
* Return: true if in pg transition, false otherwise
*/
static bool mei_txe_pg_in_transition(struct mei_device *dev)
{
return dev->pg_event == MEI_PG_EVENT_WAIT;
}
/**
* mei_txe_pg_is_enabled - detect if PG is supported by HW
*
@ -1138,6 +1150,7 @@ static const struct mei_hw_ops mei_txe_hw_ops = {
.hw_config = mei_txe_hw_config,
.hw_start = mei_txe_hw_start,
.pg_in_transition = mei_txe_pg_in_transition,
.pg_is_enabled = mei_txe_pg_is_enabled,
.intr_clear = mei_txe_intr_clear,

View File

@ -361,13 +361,15 @@ bool mei_write_is_idle(struct mei_device *dev)
{
bool idle = (dev->dev_state == MEI_DEV_ENABLED &&
list_empty(&dev->ctrl_wr_list.list) &&
list_empty(&dev->write_list.list));
list_empty(&dev->write_list.list) &&
list_empty(&dev->write_waiting_list.list));
dev_dbg(dev->dev, "write pg: is idle[%d] state=%s ctrl=%d write=%d\n",
dev_dbg(dev->dev, "write pg: is idle[%d] state=%s ctrl=%01d write=%01d wwait=%01d\n",
idle,
mei_dev_state_str(dev->dev_state),
list_empty(&dev->ctrl_wr_list.list),
list_empty(&dev->write_list.list));
list_empty(&dev->write_list.list),
list_empty(&dev->write_waiting_list.list));
return idle;
}

View File

@ -65,8 +65,8 @@ EXPORT_SYMBOL_GPL(mei_irq_compl_handler);
static inline int mei_cl_hbm_equal(struct mei_cl *cl,
struct mei_msg_hdr *mei_hdr)
{
return cl->host_client_id == mei_hdr->host_addr &&
cl->me_client_id == mei_hdr->me_addr;
return mei_cl_host_addr(cl) == mei_hdr->host_addr &&
mei_cl_me_id(cl) == mei_hdr->me_addr;
}
/**
@ -180,56 +180,14 @@ static int mei_cl_irq_disconnect_rsp(struct mei_cl *cl, struct mei_cl_cb *cb,
return -EMSGSIZE;
ret = mei_hbm_cl_disconnect_rsp(dev, cl);
cl->state = MEI_FILE_DISCONNECTED;
cl->status = 0;
mei_cl_set_disconnected(cl);
mei_io_cb_free(cb);
mei_me_cl_put(cl->me_cl);
cl->me_cl = NULL;
return ret;
}
/**
* mei_cl_irq_disconnect - processes close related operation from
* interrupt thread context - send disconnect request
*
* @cl: client
* @cb: callback block.
* @cmpl_list: complete list.
*
* Return: 0, OK; otherwise, error.
*/
static int mei_cl_irq_disconnect(struct mei_cl *cl, struct mei_cl_cb *cb,
struct mei_cl_cb *cmpl_list)
{
struct mei_device *dev = cl->dev;
u32 msg_slots;
int slots;
msg_slots = mei_data2slots(sizeof(struct hbm_client_connect_request));
slots = mei_hbuf_empty_slots(dev);
if (slots < msg_slots)
return -EMSGSIZE;
if (mei_hbm_cl_disconnect_req(dev, cl)) {
cl->status = 0;
cb->buf_idx = 0;
list_move_tail(&cb->list, &cmpl_list->list);
return -EIO;
}
cl->state = MEI_FILE_DISCONNECTING;
cl->status = 0;
cb->buf_idx = 0;
list_move_tail(&cb->list, &dev->ctrl_rd_list.list);
cl->timer_count = MEI_CONNECT_TIMEOUT;
return 0;
}
/**
* mei_cl_irq_read - processes client read related operation from the
* interrupt thread context - request for flow control credits
@ -267,49 +225,6 @@ static int mei_cl_irq_read(struct mei_cl *cl, struct mei_cl_cb *cb,
return 0;
}
/**
* mei_cl_irq_connect - send connect request in irq_thread context
*
* @cl: client
* @cb: callback block.
* @cmpl_list: complete list.
*
* Return: 0, OK; otherwise, error.
*/
static int mei_cl_irq_connect(struct mei_cl *cl, struct mei_cl_cb *cb,
struct mei_cl_cb *cmpl_list)
{
struct mei_device *dev = cl->dev;
u32 msg_slots;
int slots;
int ret;
msg_slots = mei_data2slots(sizeof(struct hbm_client_connect_request));
slots = mei_hbuf_empty_slots(dev);
if (mei_cl_is_other_connecting(cl))
return 0;
if (slots < msg_slots)
return -EMSGSIZE;
cl->state = MEI_FILE_CONNECTING;
ret = mei_hbm_cl_connect_req(dev, cl);
if (ret) {
cl->status = ret;
cb->buf_idx = 0;
list_del_init(&cb->list);
return ret;
}
list_move_tail(&cb->list, &dev->ctrl_rd_list.list);
cl->timer_count = MEI_CONNECT_TIMEOUT;
return 0;
}
/**
* mei_irq_read_handler - bottom half read routine after ISR to
* handle the read processing.

View File

@ -94,7 +94,7 @@ static int mei_release(struct inode *inode, struct file *file)
{
struct mei_cl *cl = file->private_data;
struct mei_device *dev;
int rets = 0;
int rets;
if (WARN_ON(!cl || !cl->dev))
return -ENODEV;
@ -106,11 +106,8 @@ static int mei_release(struct inode *inode, struct file *file)
rets = mei_amthif_release(dev, file);
goto out;
}
if (mei_cl_is_connected(cl)) {
cl->state = MEI_FILE_DISCONNECTING;
cl_dbg(dev, cl, "disconnecting\n");
rets = mei_cl_disconnect(cl);
}
rets = mei_cl_disconnect(cl);
mei_cl_flush_queues(cl, file);
cl_dbg(dev, cl, "removing\n");
@ -186,8 +183,7 @@ static ssize_t mei_read(struct file *file, char __user *ubuf,
err = mei_cl_read_start(cl, length, file);
if (err && err != -EBUSY) {
dev_dbg(dev->dev,
"mei start read failure with status = %d\n", err);
cl_dbg(dev, cl, "mei start read failure status = %d\n", err);
rets = err;
goto out;
}
@ -218,6 +214,11 @@ static ssize_t mei_read(struct file *file, char __user *ubuf,
cb = mei_cl_read_cb(cl, file);
if (!cb) {
if (mei_cl_is_fixed_address(cl) && dev->allow_fixed_address) {
cb = mei_cl_read_cb(cl, NULL);
if (cb)
goto copy_buffer;
}
rets = 0;
goto out;
}
@ -226,11 +227,11 @@ copy_buffer:
/* now copy the data to user space */
if (cb->status) {
rets = cb->status;
dev_dbg(dev->dev, "read operation failed %d\n", rets);
cl_dbg(dev, cl, "read operation failed %d\n", rets);
goto free;
}
dev_dbg(dev->dev, "buf.size = %d buf.idx= %ld\n",
cl_dbg(dev, cl, "buf.size = %d buf.idx = %ld\n",
cb->buf.size, cb->buf_idx);
if (length == 0 || ubuf == NULL || *offset > cb->buf_idx) {
rets = -EMSGSIZE;
@ -256,7 +257,7 @@ free:
mei_io_cb_free(cb);
out:
dev_dbg(dev->dev, "end mei read rets= %d\n", rets);
cl_dbg(dev, cl, "end mei read rets = %d\n", rets);
mutex_unlock(&dev->device_lock);
return rets;
}
@ -274,7 +275,6 @@ static ssize_t mei_write(struct file *file, const char __user *ubuf,
size_t length, loff_t *offset)
{
struct mei_cl *cl = file->private_data;
struct mei_me_client *me_cl = NULL;
struct mei_cl_cb *write_cb = NULL;
struct mei_device *dev;
unsigned long timeout = 0;
@ -292,27 +292,27 @@ static ssize_t mei_write(struct file *file, const char __user *ubuf,
goto out;
}
me_cl = mei_me_cl_by_uuid_id(dev, &cl->cl_uuid, cl->me_client_id);
if (!me_cl) {
if (!mei_cl_is_connected(cl)) {
cl_err(dev, cl, "is not connected");
rets = -ENODEV;
goto out;
}
if (!mei_me_cl_is_active(cl->me_cl)) {
rets = -ENOTTY;
goto out;
}
if (length > mei_cl_mtu(cl)) {
rets = -EFBIG;
goto out;
}
if (length == 0) {
rets = 0;
goto out;
}
if (length > me_cl->props.max_msg_length) {
rets = -EFBIG;
goto out;
}
if (!mei_cl_is_connected(cl)) {
cl_err(dev, cl, "is not connected");
rets = -ENODEV;
goto out;
}
if (cl == &dev->iamthif_cl) {
write_cb = mei_amthif_find_read_list_entry(dev, file);
@ -350,14 +350,12 @@ static ssize_t mei_write(struct file *file, const char __user *ubuf,
"amthif write failed with status = %d\n", rets);
goto out;
}
mei_me_cl_put(me_cl);
mutex_unlock(&dev->device_lock);
return length;
}
rets = mei_cl_write(cl, write_cb, false);
out:
mei_me_cl_put(me_cl);
mutex_unlock(&dev->device_lock);
if (rets < 0)
mei_io_cb_free(write_cb);
@ -395,17 +393,16 @@ static int mei_ioctl_connect_client(struct file *file,
/* find ME client we're trying to connect to */
me_cl = mei_me_cl_by_uuid(dev, &data->in_client_uuid);
if (!me_cl || me_cl->props.fixed_address) {
if (!me_cl ||
(me_cl->props.fixed_address && !dev->allow_fixed_address)) {
dev_dbg(dev->dev, "Cannot connect to FW Client UUID = %pUl\n",
&data->in_client_uuid);
&data->in_client_uuid);
mei_me_cl_put(me_cl);
return -ENOTTY;
}
cl->me_client_id = me_cl->client_id;
cl->cl_uuid = me_cl->props.protocol_name;
dev_dbg(dev->dev, "Connect to FW Client ID = %d\n",
cl->me_client_id);
me_cl->client_id);
dev_dbg(dev->dev, "FW Client - Protocol Version = %d\n",
me_cl->props.protocol_version);
dev_dbg(dev->dev, "FW Client - Max Msg Len = %d\n",
@ -441,7 +438,7 @@ static int mei_ioctl_connect_client(struct file *file,
client->protocol_version = me_cl->props.protocol_version;
dev_dbg(dev->dev, "Can connect?\n");
rets = mei_cl_connect(cl, file);
rets = mei_cl_connect(cl, me_cl, file);
end:
mei_me_cl_put(me_cl);

View File

@ -88,7 +88,8 @@ enum file_state {
MEI_FILE_CONNECTING,
MEI_FILE_CONNECTED,
MEI_FILE_DISCONNECTING,
MEI_FILE_DISCONNECTED
MEI_FILE_DISCONNECT_REPLY,
MEI_FILE_DISCONNECTED,
};
/* MEI device states */
@ -176,6 +177,8 @@ struct mei_fw_status {
* @props: client properties
* @client_id: me client id
* @mei_flow_ctrl_creds: flow control credits
* @connect_count: number connections to this client
* @reserved: reserved
*/
struct mei_me_client {
struct list_head list;
@ -183,6 +186,8 @@ struct mei_me_client {
struct mei_client_properties props;
u8 client_id;
u8 mei_flow_ctrl_creds;
u8 connect_count;
u8 reserved;
};
@ -226,11 +231,11 @@ struct mei_cl_cb {
* @rx_wait: wait queue for rx completion
* @wait: wait queue for management operation
* @status: connection status
* @cl_uuid: client uuid name
* @me_cl: fw client connected
* @host_client_id: host id
* @me_client_id: me/fw id
* @mei_flow_ctrl_creds: transmit flow credentials
* @timer_count: watchdog timer for operation completion
* @reserved: reserved for alignment
* @writing_state: state of the tx
* @rd_pending: pending read credits
* @rd_completed: completed read
@ -246,11 +251,11 @@ struct mei_cl {
wait_queue_head_t rx_wait;
wait_queue_head_t wait;
int status;
uuid_le cl_uuid;
struct mei_me_client *me_cl;
u8 host_client_id;
u8 me_client_id;
u8 mei_flow_ctrl_creds;
u8 timer_count;
u8 reserved;
enum mei_file_transaction_states writing_state;
struct list_head rd_pending;
struct list_head rd_completed;
@ -271,6 +276,7 @@ struct mei_cl {
* @fw_status : get fw status registers
* @pg_state : power gating state of the device
* @pg_in_transition : is device now in pg transition
* @pg_is_enabled : is power gating enabled
* @intr_clear : clear pending interrupts
@ -300,6 +306,7 @@ struct mei_hw_ops {
int (*fw_status)(struct mei_device *dev, struct mei_fw_status *fw_sts);
enum mei_pg_state (*pg_state)(struct mei_device *dev);
bool (*pg_in_transition)(struct mei_device *dev);
bool (*pg_is_enabled)(struct mei_device *dev);
void (*intr_clear)(struct mei_device *dev);
@ -323,34 +330,14 @@ struct mei_hw_ops {
/* MEI bus API*/
/**
* struct mei_cl_ops - MEI CL device ops
* This structure allows ME host clients to implement technology
* specific operations.
*
* @enable: Enable an MEI CL device. Some devices require specific
* HECI commands to initialize completely.
* @disable: Disable an MEI CL device.
* @send: Tx hook for the device. This allows ME host clients to trap
* the device driver buffers before actually physically
* pushing it to the ME.
* @recv: Rx hook for the device. This allows ME host clients to trap the
* ME buffers before forwarding them to the device driver.
*/
struct mei_cl_ops {
int (*enable)(struct mei_cl_device *device);
int (*disable)(struct mei_cl_device *device);
int (*send)(struct mei_cl_device *device, u8 *buf, size_t length);
int (*recv)(struct mei_cl_device *device, u8 *buf, size_t length);
};
struct mei_cl_device *mei_cl_add_device(struct mei_device *dev,
uuid_le uuid, char *name,
struct mei_cl_ops *ops);
struct mei_me_client *me_cl,
struct mei_cl *cl,
char *name);
void mei_cl_remove_device(struct mei_cl_device *device);
ssize_t __mei_cl_async_send(struct mei_cl *cl, u8 *buf, size_t length);
ssize_t __mei_cl_send(struct mei_cl *cl, u8 *buf, size_t length);
ssize_t __mei_cl_send(struct mei_cl *cl, u8 *buf, size_t length,
bool blocking);
ssize_t __mei_cl_recv(struct mei_cl *cl, u8 *buf, size_t length);
void mei_cl_bus_rx_event(struct mei_cl *cl);
void mei_cl_bus_remove_devices(struct mei_device *dev);
@ -358,51 +345,21 @@ int mei_cl_bus_init(void);
void mei_cl_bus_exit(void);
struct mei_cl *mei_cl_bus_find_cl_by_uuid(struct mei_device *dev, uuid_le uuid);
/**
* struct mei_cl_device - MEI device handle
* An mei_cl_device pointer is returned from mei_add_device()
* and links MEI bus clients to their actual ME host client pointer.
* Drivers for MEI devices will get an mei_cl_device pointer
* when being probed and shall use it for doing ME bus I/O.
*
* @dev: linux driver model device pointer
* @cl: mei client
* @ops: ME transport ops
* @event_work: async work to execute event callback
* @event_cb: Drivers register this callback to get asynchronous ME
* events (e.g. Rx buffer pending) notifications.
* @event_context: event callback run context
* @events: Events bitmask sent to the driver.
* @priv_data: client private data
*/
struct mei_cl_device {
struct device dev;
struct mei_cl *cl;
const struct mei_cl_ops *ops;
struct work_struct event_work;
mei_cl_event_cb_t event_cb;
void *event_context;
unsigned long events;
void *priv_data;
};
/**
* enum mei_pg_event - power gating transition events
*
* @MEI_PG_EVENT_IDLE: the driver is not in power gating transition
* @MEI_PG_EVENT_WAIT: the driver is waiting for a pg event to complete
* @MEI_PG_EVENT_RECEIVED: the driver received pg event
* @MEI_PG_EVENT_INTR_WAIT: the driver is waiting for a pg event interrupt
* @MEI_PG_EVENT_INTR_RECEIVED: the driver received pg event interrupt
*/
enum mei_pg_event {
MEI_PG_EVENT_IDLE,
MEI_PG_EVENT_WAIT,
MEI_PG_EVENT_RECEIVED,
MEI_PG_EVENT_INTR_WAIT,
MEI_PG_EVENT_INTR_RECEIVED,
};
/**
@ -467,6 +424,8 @@ const char *mei_pg_state_str(enum mei_pg_state state);
* @host_clients_map : host clients id pool
* @me_client_index : last FW client index in enumeration
*
* @allow_fixed_address: allow user space to connect a fixed client
*
* @wd_cl : watchdog client
* @wd_state : watchdog client state
* @wd_pending : watchdog command is pending
@ -479,7 +438,6 @@ const char *mei_pg_state_str(enum mei_pg_state state);
* @iamthif_cl : amthif host client
* @iamthif_current_cb : amthif current operation callback
* @iamthif_open_count : number of opened amthif connections
* @iamthif_mtu : amthif client max message length
* @iamthif_timer : time stamp of current amthif command completion
* @iamthif_stall_timer : timer to detect amthif hang
* @iamthif_state : amthif processor state
@ -558,6 +516,8 @@ struct mei_device {
DECLARE_BITMAP(host_clients_map, MEI_CLIENTS_MAX);
unsigned long me_client_index;
u32 allow_fixed_address;
struct mei_cl wd_cl;
enum mei_wd_states wd_state;
bool wd_pending;
@ -573,7 +533,6 @@ struct mei_device {
struct mei_cl iamthif_cl;
struct mei_cl_cb *iamthif_current_cb;
long iamthif_open_count;
int iamthif_mtu;
unsigned long iamthif_timer;
u32 iamthif_stall_timer;
enum iamthif_states iamthif_state;
@ -652,7 +611,7 @@ void mei_irq_compl_handler(struct mei_device *dev, struct mei_cl_cb *cmpl_list);
*/
void mei_amthif_reset_params(struct mei_device *dev);
int mei_amthif_host_init(struct mei_device *dev);
int mei_amthif_host_init(struct mei_device *dev, struct mei_me_client *me_cl);
int mei_amthif_read(struct mei_device *dev, struct file *file,
char __user *ubuf, size_t length, loff_t *offset);
@ -679,7 +638,7 @@ int mei_amthif_irq_read(struct mei_device *dev, s32 *slots);
/*
* NFC functions
*/
int mei_nfc_host_init(struct mei_device *dev);
int mei_nfc_host_init(struct mei_device *dev, struct mei_me_client *me_cl);
void mei_nfc_host_exit(struct mei_device *dev);
/*
@ -689,7 +648,7 @@ extern const uuid_le mei_nfc_guid;
int mei_wd_send(struct mei_device *dev);
int mei_wd_stop(struct mei_device *dev);
int mei_wd_host_init(struct mei_device *dev);
int mei_wd_host_init(struct mei_device *dev, struct mei_me_client *me_cl);
/*
* mei_watchdog_register - Registering watchdog interface
* once we got connection to the WD Client
@ -717,6 +676,11 @@ static inline enum mei_pg_state mei_pg_state(struct mei_device *dev)
return dev->ops->pg_state(dev);
}
static inline bool mei_pg_in_transition(struct mei_device *dev)
{
return dev->ops->pg_in_transition(dev);
}
static inline bool mei_pg_is_enabled(struct mei_device *dev)
{
return dev->ops->pg_is_enabled(dev);

View File

@ -91,30 +91,25 @@ struct mei_nfc_hci_hdr {
/**
* struct mei_nfc_dev - NFC mei device
*
* @me_cl: NFC me client
* @cl: NFC host client
* @cl_info: NFC info host client
* @init_work: perform connection to the info client
* @send_wq: send completion wait queue
* @fw_ivn: NFC Interface Version Number
* @vendor_id: NFC manufacturer ID
* @radio_type: NFC radio type
* @bus_name: bus name
*
* @req_id: message counter
* @recv_req_id: reception message counter
*/
struct mei_nfc_dev {
struct mei_me_client *me_cl;
struct mei_cl *cl;
struct mei_cl *cl_info;
struct work_struct init_work;
wait_queue_head_t send_wq;
u8 fw_ivn;
u8 vendor_id;
u8 radio_type;
char *bus_name;
u16 req_id;
u16 recv_req_id;
};
/* UUIDs for NFC F/W clients */
@ -151,6 +146,7 @@ static void mei_nfc_free(struct mei_nfc_dev *ndev)
kfree(ndev->cl_info);
}
mei_me_cl_put(ndev->me_cl);
kfree(ndev);
}
@ -199,73 +195,6 @@ static int mei_nfc_build_bus_name(struct mei_nfc_dev *ndev)
return 0;
}
static int mei_nfc_connect(struct mei_nfc_dev *ndev)
{
struct mei_device *dev;
struct mei_cl *cl;
struct mei_nfc_cmd *cmd, *reply;
struct mei_nfc_connect *connect;
struct mei_nfc_connect_resp *connect_resp;
size_t connect_length, connect_resp_length;
int bytes_recv, ret;
cl = ndev->cl;
dev = cl->dev;
connect_length = sizeof(struct mei_nfc_cmd) +
sizeof(struct mei_nfc_connect);
connect_resp_length = sizeof(struct mei_nfc_cmd) +
sizeof(struct mei_nfc_connect_resp);
cmd = kzalloc(connect_length, GFP_KERNEL);
if (!cmd)
return -ENOMEM;
connect = (struct mei_nfc_connect *)cmd->data;
reply = kzalloc(connect_resp_length, GFP_KERNEL);
if (!reply) {
kfree(cmd);
return -ENOMEM;
}
connect_resp = (struct mei_nfc_connect_resp *)reply->data;
cmd->command = MEI_NFC_CMD_MAINTENANCE;
cmd->data_size = 3;
cmd->sub_command = MEI_NFC_SUBCMD_CONNECT;
connect->fw_ivn = ndev->fw_ivn;
connect->vendor_id = ndev->vendor_id;
ret = __mei_cl_send(cl, (u8 *)cmd, connect_length);
if (ret < 0) {
dev_err(dev->dev, "Could not send connect cmd\n");
goto err;
}
bytes_recv = __mei_cl_recv(cl, (u8 *)reply, connect_resp_length);
if (bytes_recv < 0) {
dev_err(dev->dev, "Could not read connect response\n");
ret = bytes_recv;
goto err;
}
dev_info(dev->dev, "IVN 0x%x Vendor ID 0x%x\n",
connect_resp->fw_ivn, connect_resp->vendor_id);
dev_info(dev->dev, "ME FW %d.%d.%d.%d\n",
connect_resp->me_major, connect_resp->me_minor,
connect_resp->me_hotfix, connect_resp->me_build);
ret = 0;
err:
kfree(reply);
kfree(cmd);
return ret;
}
static int mei_nfc_if_version(struct mei_nfc_dev *ndev)
{
struct mei_device *dev;
@ -285,7 +214,7 @@ static int mei_nfc_if_version(struct mei_nfc_dev *ndev)
cmd.data_size = 1;
cmd.sub_command = MEI_NFC_SUBCMD_IF_VERSION;
ret = __mei_cl_send(cl, (u8 *)&cmd, sizeof(struct mei_nfc_cmd));
ret = __mei_cl_send(cl, (u8 *)&cmd, sizeof(struct mei_nfc_cmd), 1);
if (ret < 0) {
dev_err(dev->dev, "Could not send IF version cmd\n");
return ret;
@ -317,106 +246,13 @@ err:
return ret;
}
static int mei_nfc_enable(struct mei_cl_device *cldev)
{
struct mei_device *dev;
struct mei_nfc_dev *ndev;
int ret;
ndev = (struct mei_nfc_dev *)cldev->priv_data;
dev = ndev->cl->dev;
ret = mei_nfc_connect(ndev);
if (ret < 0) {
dev_err(dev->dev, "Could not connect to NFC");
return ret;
}
return 0;
}
static int mei_nfc_disable(struct mei_cl_device *cldev)
{
return 0;
}
static int mei_nfc_send(struct mei_cl_device *cldev, u8 *buf, size_t length)
{
struct mei_device *dev;
struct mei_nfc_dev *ndev;
struct mei_nfc_hci_hdr *hdr;
u8 *mei_buf;
int err;
ndev = (struct mei_nfc_dev *) cldev->priv_data;
dev = ndev->cl->dev;
err = -ENOMEM;
mei_buf = kzalloc(length + MEI_NFC_HEADER_SIZE, GFP_KERNEL);
if (!mei_buf)
goto out;
hdr = (struct mei_nfc_hci_hdr *) mei_buf;
hdr->cmd = MEI_NFC_CMD_HCI_SEND;
hdr->status = 0;
hdr->req_id = ndev->req_id;
hdr->reserved = 0;
hdr->data_size = length;
memcpy(mei_buf + MEI_NFC_HEADER_SIZE, buf, length);
err = __mei_cl_send(ndev->cl, mei_buf, length + MEI_NFC_HEADER_SIZE);
if (err < 0)
goto out;
if (!wait_event_interruptible_timeout(ndev->send_wq,
ndev->recv_req_id == ndev->req_id, HZ)) {
dev_err(dev->dev, "NFC MEI command timeout\n");
err = -ETIME;
} else {
ndev->req_id++;
}
out:
kfree(mei_buf);
return err;
}
static int mei_nfc_recv(struct mei_cl_device *cldev, u8 *buf, size_t length)
{
struct mei_nfc_dev *ndev;
struct mei_nfc_hci_hdr *hci_hdr;
int received_length;
ndev = (struct mei_nfc_dev *)cldev->priv_data;
received_length = __mei_cl_recv(ndev->cl, buf, length);
if (received_length < 0)
return received_length;
hci_hdr = (struct mei_nfc_hci_hdr *) buf;
if (hci_hdr->cmd == MEI_NFC_CMD_HCI_SEND) {
ndev->recv_req_id = hci_hdr->req_id;
wake_up(&ndev->send_wq);
return 0;
}
return received_length;
}
static struct mei_cl_ops nfc_ops = {
.enable = mei_nfc_enable,
.disable = mei_nfc_disable,
.send = mei_nfc_send,
.recv = mei_nfc_recv,
};
static void mei_nfc_init(struct work_struct *work)
{
struct mei_device *dev;
struct mei_cl_device *cldev;
struct mei_nfc_dev *ndev;
struct mei_cl *cl_info;
struct mei_me_client *me_cl_info;
ndev = container_of(work, struct mei_nfc_dev, init_work);
@ -425,13 +261,22 @@ static void mei_nfc_init(struct work_struct *work)
mutex_lock(&dev->device_lock);
if (mei_cl_connect(cl_info, NULL) < 0) {
/* check for valid client id */
me_cl_info = mei_me_cl_by_uuid(dev, &mei_nfc_info_guid);
if (!me_cl_info) {
mutex_unlock(&dev->device_lock);
dev_info(dev->dev, "nfc: failed to find the info client\n");
goto err;
}
if (mei_cl_connect(cl_info, me_cl_info, NULL) < 0) {
mei_me_cl_put(me_cl_info);
mutex_unlock(&dev->device_lock);
dev_err(dev->dev, "Could not connect to the NFC INFO ME client");
goto err;
}
mei_me_cl_put(me_cl_info);
mutex_unlock(&dev->device_lock);
if (mei_nfc_if_version(ndev) < 0) {
@ -459,7 +304,8 @@ static void mei_nfc_init(struct work_struct *work)
return;
}
cldev = mei_cl_add_device(dev, mei_nfc_guid, ndev->bus_name, &nfc_ops);
cldev = mei_cl_add_device(dev, ndev->me_cl, ndev->cl,
ndev->bus_name);
if (!cldev) {
dev_err(dev->dev, "Could not add the NFC device to the MEI bus\n");
@ -479,11 +325,10 @@ err:
}
int mei_nfc_host_init(struct mei_device *dev)
int mei_nfc_host_init(struct mei_device *dev, struct mei_me_client *me_cl)
{
struct mei_nfc_dev *ndev;
struct mei_cl *cl_info, *cl;
struct mei_me_client *me_cl = NULL;
int ret;
@ -500,11 +345,9 @@ int mei_nfc_host_init(struct mei_device *dev)
goto err;
}
/* check for valid client id */
me_cl = mei_me_cl_by_uuid(dev, &mei_nfc_info_guid);
if (!me_cl) {
dev_info(dev->dev, "nfc: failed to find the client\n");
ret = -ENOTTY;
ndev->me_cl = mei_me_cl_get(me_cl);
if (!ndev->me_cl) {
ret = -ENODEV;
goto err;
}
@ -514,48 +357,26 @@ int mei_nfc_host_init(struct mei_device *dev)
goto err;
}
cl_info->me_client_id = me_cl->client_id;
cl_info->cl_uuid = me_cl->props.protocol_name;
mei_me_cl_put(me_cl);
me_cl = NULL;
list_add_tail(&cl_info->device_link, &dev->device_list);
ndev->cl_info = cl_info;
/* check for valid client id */
me_cl = mei_me_cl_by_uuid(dev, &mei_nfc_guid);
if (!me_cl) {
dev_info(dev->dev, "nfc: failed to find the client\n");
ret = -ENOTTY;
goto err;
}
cl = mei_cl_alloc_linked(dev, MEI_HOST_CLIENT_ID_ANY);
if (IS_ERR(cl)) {
ret = PTR_ERR(cl);
goto err;
}
cl->me_client_id = me_cl->client_id;
cl->cl_uuid = me_cl->props.protocol_name;
mei_me_cl_put(me_cl);
me_cl = NULL;
list_add_tail(&cl->device_link, &dev->device_list);
ndev->cl = cl;
ndev->req_id = 1;
INIT_WORK(&ndev->init_work, mei_nfc_init);
init_waitqueue_head(&ndev->send_wq);
schedule_work(&ndev->init_work);
return 0;
err:
mei_me_cl_put(me_cl);
mei_nfc_free(ndev);
return ret;

View File

@ -338,7 +338,7 @@ static int mei_txe_pm_runtime_suspend(struct device *device)
* However if device is not wakeable we do not enter
* D-low state and we need to keep the interrupt kicking
*/
if (!ret && pci_dev_run_wake(pdev))
if (!ret && pci_dev_run_wake(pdev))
mei_disable_interrupts(dev);
dev_dbg(&pdev->dev, "rpm: txe: runtime suspend ret=%d\n", ret);

View File

@ -50,15 +50,15 @@ static void mei_wd_set_start_timeout(struct mei_device *dev, u16 timeout)
* mei_wd_host_init - connect to the watchdog client
*
* @dev: the device structure
* @me_cl: me client
*
* Return: -ENOTTY if wd client cannot be found
* -EIO if write has failed
* 0 on success
*/
int mei_wd_host_init(struct mei_device *dev)
int mei_wd_host_init(struct mei_device *dev, struct mei_me_client *me_cl)
{
struct mei_cl *cl = &dev->wd_cl;
struct mei_me_client *me_cl;
int ret;
mei_cl_init(cl, dev);
@ -66,27 +66,13 @@ int mei_wd_host_init(struct mei_device *dev)
dev->wd_timeout = MEI_WD_DEFAULT_TIMEOUT;
dev->wd_state = MEI_WD_IDLE;
/* check for valid client id */
me_cl = mei_me_cl_by_uuid(dev, &mei_wd_guid);
if (!me_cl) {
dev_info(dev->dev, "wd: failed to find the client\n");
return -ENOTTY;
}
cl->me_client_id = me_cl->client_id;
cl->cl_uuid = me_cl->props.protocol_name;
mei_me_cl_put(me_cl);
ret = mei_cl_link(cl, MEI_WD_HOST_CLIENT_ID);
if (ret < 0) {
dev_info(dev->dev, "wd: failed link client\n");
return ret;
}
ret = mei_cl_connect(cl, NULL);
ret = mei_cl_connect(cl, me_cl, NULL);
if (ret) {
dev_err(dev->dev, "wd: failed to connect = %d\n", ret);
mei_cl_unlink(cl);
@ -118,7 +104,7 @@ int mei_wd_send(struct mei_device *dev)
int ret;
hdr.host_addr = cl->host_client_id;
hdr.me_addr = cl->me_client_id;
hdr.me_addr = mei_cl_me_id(cl);
hdr.msg_complete = 1;
hdr.reserved = 0;
hdr.internal = 0;

View File

@ -15,11 +15,28 @@ config INTEL_MIC_BUS
OS and tools for MIC to use with this driver are available from
<http://software.intel.com/en-us/mic-developer>.
comment "SCIF Bus Driver"
config SCIF_BUS
tristate "SCIF Bus Driver"
depends on 64BIT && PCI && X86 && X86_DEV_DMA_OPS
help
This option is selected by any driver which registers a
device or driver on the SCIF Bus, such as CONFIG_INTEL_MIC_HOST
and CONFIG_INTEL_MIC_CARD.
If you are building a host/card kernel with an Intel MIC device
then say M (recommended) or Y, else say N. If unsure say N.
More information about the Intel MIC family as well as the Linux
OS and tools for MIC to use with this driver are available from
<http://software.intel.com/en-us/mic-developer>.
comment "Intel MIC Host Driver"
config INTEL_MIC_HOST
tristate "Intel MIC Host Driver"
depends on 64BIT && PCI && X86 && INTEL_MIC_BUS
depends on 64BIT && PCI && X86 && INTEL_MIC_BUS && SCIF_BUS
select VHOST_RING
help
This enables Host Driver support for the Intel Many Integrated
@ -39,7 +56,7 @@ comment "Intel MIC Card Driver"
config INTEL_MIC_CARD
tristate "Intel MIC Card Driver"
depends on 64BIT && X86 && INTEL_MIC_BUS
depends on 64BIT && X86 && INTEL_MIC_BUS && SCIF_BUS
select VIRTIO
help
This enables card driver support for the Intel Many Integrated
@ -52,3 +69,22 @@ config INTEL_MIC_CARD
For more information see
<http://software.intel.com/en-us/mic-developer>.
comment "SCIF Driver"
config SCIF
tristate "SCIF Driver"
depends on 64BIT && PCI && X86 && SCIF_BUS
help
This enables SCIF Driver support for the Intel Many Integrated
Core (MIC) family of PCIe form factor coprocessor devices that
run a 64 bit Linux OS. The Symmetric Communication Interface
(SCIF (pronounced as skiff)) is a low level communications API
across PCIe currently implemented for MIC.
If you are building a host kernel with an Intel MIC device then
say M (recommended) or Y, else say N. If unsure say N.
More information about the Intel MIC family as well as the Linux
OS and tools for MIC to use with this driver are available from
<http://software.intel.com/en-us/mic-developer>.

View File

@ -4,4 +4,5 @@
#
obj-$(CONFIG_INTEL_MIC_HOST) += host/
obj-$(CONFIG_INTEL_MIC_CARD) += card/
obj-$(CONFIG_INTEL_MIC_BUS) += bus/
obj-y += bus/
obj-$(CONFIG_SCIF) += scif/

View File

@ -3,3 +3,4 @@
# Copyright(c) 2014, Intel Corporation.
#
obj-$(CONFIG_INTEL_MIC_BUS) += mic_bus.o
obj-$(CONFIG_SCIF_BUS) += scif_bus.o

View File

@ -0,0 +1,210 @@
/*
* Intel MIC Platform Software Stack (MPSS)
*
* Copyright(c) 2014 Intel Corporation.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2, as
* published by the Free Software Foundation.
*
* 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.
*
* Intel Symmetric Communications Interface Bus driver.
*/
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/idr.h>
#include <linux/dma-mapping.h>
#include "scif_bus.h"
static ssize_t device_show(struct device *d,
struct device_attribute *attr, char *buf)
{
struct scif_hw_dev *dev = dev_to_scif(d);
return sprintf(buf, "0x%04x\n", dev->id.device);
}
static DEVICE_ATTR_RO(device);
static ssize_t vendor_show(struct device *d,
struct device_attribute *attr, char *buf)
{
struct scif_hw_dev *dev = dev_to_scif(d);
return sprintf(buf, "0x%04x\n", dev->id.vendor);
}
static DEVICE_ATTR_RO(vendor);
static ssize_t modalias_show(struct device *d,
struct device_attribute *attr, char *buf)
{
struct scif_hw_dev *dev = dev_to_scif(d);
return sprintf(buf, "scif:d%08Xv%08X\n",
dev->id.device, dev->id.vendor);
}
static DEVICE_ATTR_RO(modalias);
static struct attribute *scif_dev_attrs[] = {
&dev_attr_device.attr,
&dev_attr_vendor.attr,
&dev_attr_modalias.attr,
NULL,
};
ATTRIBUTE_GROUPS(scif_dev);
static inline int scif_id_match(const struct scif_hw_dev *dev,
const struct scif_hw_dev_id *id)
{
if (id->device != dev->id.device && id->device != SCIF_DEV_ANY_ID)
return 0;
return id->vendor == SCIF_DEV_ANY_ID || id->vendor == dev->id.vendor;
}
/*
* This looks through all the IDs a driver claims to support. If any of them
* match, we return 1 and the kernel will call scif_dev_probe().
*/
static int scif_dev_match(struct device *dv, struct device_driver *dr)
{
unsigned int i;
struct scif_hw_dev *dev = dev_to_scif(dv);
const struct scif_hw_dev_id *ids;
ids = drv_to_scif(dr)->id_table;
for (i = 0; ids[i].device; i++)
if (scif_id_match(dev, &ids[i]))
return 1;
return 0;
}
static int scif_uevent(struct device *dv, struct kobj_uevent_env *env)
{
struct scif_hw_dev *dev = dev_to_scif(dv);
return add_uevent_var(env, "MODALIAS=scif:d%08Xv%08X",
dev->id.device, dev->id.vendor);
}
static int scif_dev_probe(struct device *d)
{
struct scif_hw_dev *dev = dev_to_scif(d);
struct scif_driver *drv = drv_to_scif(dev->dev.driver);
return drv->probe(dev);
}
static int scif_dev_remove(struct device *d)
{
struct scif_hw_dev *dev = dev_to_scif(d);
struct scif_driver *drv = drv_to_scif(dev->dev.driver);
drv->remove(dev);
return 0;
}
static struct bus_type scif_bus = {
.name = "scif_bus",
.match = scif_dev_match,
.dev_groups = scif_dev_groups,
.uevent = scif_uevent,
.probe = scif_dev_probe,
.remove = scif_dev_remove,
};
int scif_register_driver(struct scif_driver *driver)
{
driver->driver.bus = &scif_bus;
return driver_register(&driver->driver);
}
EXPORT_SYMBOL_GPL(scif_register_driver);
void scif_unregister_driver(struct scif_driver *driver)
{
driver_unregister(&driver->driver);
}
EXPORT_SYMBOL_GPL(scif_unregister_driver);
static void scif_release_dev(struct device *d)
{
struct scif_hw_dev *sdev = dev_to_scif(d);
kfree(sdev);
}
struct scif_hw_dev *
scif_register_device(struct device *pdev, int id, struct dma_map_ops *dma_ops,
struct scif_hw_ops *hw_ops, u8 dnode, u8 snode,
struct mic_mw *mmio, struct mic_mw *aper, void *dp,
void __iomem *rdp, struct dma_chan **chan, int num_chan)
{
int ret;
struct scif_hw_dev *sdev;
sdev = kzalloc(sizeof(*sdev), GFP_KERNEL);
if (!sdev)
return ERR_PTR(-ENOMEM);
sdev->dev.parent = pdev;
sdev->id.device = id;
sdev->id.vendor = SCIF_DEV_ANY_ID;
sdev->dev.archdata.dma_ops = dma_ops;
sdev->dev.release = scif_release_dev;
sdev->hw_ops = hw_ops;
sdev->dnode = dnode;
sdev->snode = snode;
dev_set_drvdata(&sdev->dev, sdev);
sdev->dev.bus = &scif_bus;
sdev->mmio = mmio;
sdev->aper = aper;
sdev->dp = dp;
sdev->rdp = rdp;
sdev->dev.dma_mask = &sdev->dev.coherent_dma_mask;
dma_set_mask(&sdev->dev, DMA_BIT_MASK(64));
sdev->dma_ch = chan;
sdev->num_dma_ch = num_chan;
dev_set_name(&sdev->dev, "scif-dev%u", sdev->dnode);
/*
* device_register() causes the bus infrastructure to look for a
* matching driver.
*/
ret = device_register(&sdev->dev);
if (ret)
goto free_sdev;
return sdev;
free_sdev:
kfree(sdev);
return ERR_PTR(ret);
}
EXPORT_SYMBOL_GPL(scif_register_device);
void scif_unregister_device(struct scif_hw_dev *sdev)
{
device_unregister(&sdev->dev);
}
EXPORT_SYMBOL_GPL(scif_unregister_device);
static int __init scif_init(void)
{
return bus_register(&scif_bus);
}
static void __exit scif_exit(void)
{
bus_unregister(&scif_bus);
}
core_initcall(scif_init);
module_exit(scif_exit);
MODULE_AUTHOR("Intel Corporation");
MODULE_DESCRIPTION("Intel(R) SCIF Bus driver");
MODULE_LICENSE("GPL v2");

View File

@ -0,0 +1,129 @@
/*
* Intel MIC Platform Software Stack (MPSS)
*
* Copyright(c) 2014 Intel Corporation.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2, as
* published by the Free Software Foundation.
*
* 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.
*
* Intel Symmetric Communications Interface Bus driver.
*/
#ifndef _SCIF_BUS_H_
#define _SCIF_BUS_H_
/*
* Everything a scif driver needs to work with any particular scif
* hardware abstraction layer.
*/
#include <linux/dma-mapping.h>
#include <linux/mic_common.h>
#include "../common/mic_dev.h"
struct scif_hw_dev_id {
u32 device;
u32 vendor;
};
#define MIC_SCIF_DEV 1
#define SCIF_DEV_ANY_ID 0xffffffff
/**
* scif_hw_dev - representation of a hardware device abstracted for scif
* @hw_ops: the hardware ops supported by this device
* @id: the device type identification (used to match it with a driver)
* @mmio: MMIO memory window
* @aper: Aperture memory window
* @dev: underlying device
* @dnode - The destination node which this device will communicate with.
* @snode - The source node for this device.
* @dp - Self device page
* @rdp - Remote device page
* @dma_ch - Array of DMA channels
* @num_dma_ch - Number of DMA channels available
*/
struct scif_hw_dev {
struct scif_hw_ops *hw_ops;
struct scif_hw_dev_id id;
struct mic_mw *mmio;
struct mic_mw *aper;
struct device dev;
u8 dnode;
u8 snode;
void *dp;
void __iomem *rdp;
struct dma_chan **dma_ch;
int num_dma_ch;
};
/**
* scif_driver - operations for a scif I/O driver
* @driver: underlying device driver (populate name and owner).
* @id_table: the ids serviced by this driver.
* @probe: the function to call when a device is found. Returns 0 or -errno.
* @remove: the function to call when a device is removed.
*/
struct scif_driver {
struct device_driver driver;
const struct scif_hw_dev_id *id_table;
int (*probe)(struct scif_hw_dev *dev);
void (*remove)(struct scif_hw_dev *dev);
};
/**
* scif_hw_ops - Hardware operations for accessing a SCIF device on the SCIF bus.
*
* @next_db: Obtain the next available doorbell.
* @request_irq: Request an interrupt on a particular doorbell.
* @free_irq: Free an interrupt requested previously.
* @ack_interrupt: acknowledge an interrupt in the ISR.
* @send_intr: Send an interrupt to the remote node on a specified doorbell.
* @send_p2p_intr: Send an interrupt to the peer node on a specified doorbell
* which is specifically targeted for a peer to peer node.
* @ioremap: Map a buffer with the specified physical address and length.
* @iounmap: Unmap a buffer previously mapped.
*/
struct scif_hw_ops {
int (*next_db)(struct scif_hw_dev *sdev);
struct mic_irq * (*request_irq)(struct scif_hw_dev *sdev,
irqreturn_t (*func)(int irq,
void *data),
const char *name, void *data,
int db);
void (*free_irq)(struct scif_hw_dev *sdev,
struct mic_irq *cookie, void *data);
void (*ack_interrupt)(struct scif_hw_dev *sdev, int num);
void (*send_intr)(struct scif_hw_dev *sdev, int db);
void (*send_p2p_intr)(struct scif_hw_dev *sdev, int db,
struct mic_mw *mw);
void __iomem * (*ioremap)(struct scif_hw_dev *sdev,
phys_addr_t pa, size_t len);
void (*iounmap)(struct scif_hw_dev *sdev, void __iomem *va);
};
int scif_register_driver(struct scif_driver *driver);
void scif_unregister_driver(struct scif_driver *driver);
struct scif_hw_dev *
scif_register_device(struct device *pdev, int id,
struct dma_map_ops *dma_ops,
struct scif_hw_ops *hw_ops, u8 dnode, u8 snode,
struct mic_mw *mmio, struct mic_mw *aper,
void *dp, void __iomem *rdp,
struct dma_chan **chan, int num_chan);
void scif_unregister_device(struct scif_hw_dev *sdev);
static inline struct scif_hw_dev *dev_to_scif(struct device *dev)
{
return container_of(dev, struct scif_hw_dev, dev);
}
static inline struct scif_driver *drv_to_scif(struct device_driver *drv)
{
return container_of(drv, struct scif_driver, driver);
}
#endif /* _SCIF_BUS_H */

Some files were not shown because too many files have changed in this diff Show More