char/misc driver patches for 4.11-rc1
Here is the big char/misc driver patchset for 4.11-rc1. Lots of different driver subsystems updated here. Rework for the hyperv subsystem to handle new platforms better, mei and w1 and extcon driver updates, as well as a number of other "minor" driver updates. Full details are in the shortlog below. All of these have been in linux-next for a while with no reported issues. Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> -----BEGIN PGP SIGNATURE----- iG0EABECAC0WIQT0tgzFv3jCIUoxPcsxR9QN2y37KQUCWK2iRQ8cZ3JlZ0Brcm9h aC5jb20ACgkQMUfUDdst+ynhFACguVE+/ixj5u5bT5DXQaZNai/6zIAAmgMWwd/t YTD2cwsJsGbTT1fY3SUe =CiSI -----END PGP SIGNATURE----- Merge tag 'char-misc-4.11-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc Pull char/misc driver updates from Greg KH: "Here is the big char/misc driver patchset for 4.11-rc1. Lots of different driver subsystems updated here: rework for the hyperv subsystem to handle new platforms better, mei and w1 and extcon driver updates, as well as a number of other "minor" driver updates. All of these have been in linux-next for a while with no reported issues" * tag 'char-misc-4.11-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc: (169 commits) goldfish: Sanitize the broken interrupt handler x86/platform/goldfish: Prevent unconditional loading vmbus: replace modulus operation with subtraction vmbus: constify parameters where possible vmbus: expose hv_begin/end_read vmbus: remove conditional locking of vmbus_write vmbus: add direct isr callback mode vmbus: change to per channel tasklet vmbus: put related per-cpu variable together vmbus: callback is in softirq not workqueue binder: Add support for file-descriptor arrays binder: Add support for scatter-gather binder: Add extra size to allocator binder: Refactor binder_transact() binder: Support multiple /dev instances binder: Deal with contexts in debugfs binder: Support multiple context managers binder: Split flat_binder_object auxdisplay: ht16k33: remove private workqueue auxdisplay: ht16k33: rework input device initialization ...
This commit is contained in:
commit
e30aee9e10
|
@ -11,7 +11,7 @@ DOCBOOKS := z8530book.xml \
|
||||||
writing_usb_driver.xml networking.xml \
|
writing_usb_driver.xml networking.xml \
|
||||||
kernel-api.xml filesystems.xml lsm.xml kgdb.xml \
|
kernel-api.xml filesystems.xml lsm.xml kgdb.xml \
|
||||||
gadget.xml libata.xml mtdnand.xml librs.xml rapidio.xml \
|
gadget.xml libata.xml mtdnand.xml librs.xml rapidio.xml \
|
||||||
genericirq.xml s390-drivers.xml uio-howto.xml scsi.xml \
|
genericirq.xml s390-drivers.xml scsi.xml \
|
||||||
sh.xml regulator.xml w1.xml \
|
sh.xml regulator.xml w1.xml \
|
||||||
writing_musb_glue_layer.xml iio.xml
|
writing_musb_glue_layer.xml iio.xml
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1195,6 +1195,10 @@
|
||||||
When zero, profiling data is discarded and associated
|
When zero, profiling data is discarded and associated
|
||||||
debugfs files are removed at module unload time.
|
debugfs files are removed at module unload time.
|
||||||
|
|
||||||
|
goldfish [X86] Enable the goldfish android emulator platform.
|
||||||
|
Don't use this when you are not running on the
|
||||||
|
android emulator
|
||||||
|
|
||||||
gpt [EFI] Forces disk with valid GPT signature but
|
gpt [EFI] Forces disk with valid GPT signature but
|
||||||
invalid Protective MBR to be treated as GPT. If the
|
invalid Protective MBR to be treated as GPT. If the
|
||||||
primary GPT is corrupted, it enables the backup/alternate
|
primary GPT is corrupted, it enables the backup/alternate
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
EEPROM / CSR SMBus-slave interface of IDT 89HPESx devices
|
||||||
|
|
||||||
|
Required properties:
|
||||||
|
- compatible : should be "<manufacturer>,<type>"
|
||||||
|
Basically there is only one manufacturer: idt, but some
|
||||||
|
compatible devices may be produced in future. Following devices
|
||||||
|
are supported: 89hpes8nt2, 89hpes12nt3, 89hpes24nt6ag2,
|
||||||
|
89hpes32nt8ag2, 89hpes32nt8bg2, 89hpes12nt12g2, 89hpes16nt16g2,
|
||||||
|
89hpes24nt24g2, 89hpes32nt24ag2, 89hpes32nt24bg2;
|
||||||
|
89hpes12n3, 89hpes12n3a, 89hpes24n3, 89hpes24n3a;
|
||||||
|
89hpes32h8, 89hpes32h8g2, 89hpes48h12, 89hpes48h12g2,
|
||||||
|
89hpes48h12ag2, 89hpes16h16, 89hpes22h16, 89hpes22h16g2,
|
||||||
|
89hpes34h16, 89hpes34h16g2, 89hpes64h16, 89hpes64h16g2,
|
||||||
|
89hpes64h16ag2;
|
||||||
|
89hpes12t3g2, 89hpes24t3g2, 89hpes16t4, 89hpes4t4g2,
|
||||||
|
89hpes10t4g2, 89hpes16t4g2, 89hpes16t4ag2, 89hpes5t5,
|
||||||
|
89hpes6t5, 89hpes8t5, 89hpes8t5a, 89hpes24t6, 89hpes6t6g2,
|
||||||
|
89hpes24t6g2, 89hpes16t7, 89hpes32t8, 89hpes32t8g2,
|
||||||
|
89hpes48t12, 89hpes48t12g2.
|
||||||
|
- reg : I2C address of the IDT 89HPESx device.
|
||||||
|
|
||||||
|
Optionally there can be EEPROM-compatible subnode:
|
||||||
|
- compatible: There are five EEPROM devices supported: 24c32, 24c64, 24c128,
|
||||||
|
24c256 and 24c512 differed by size.
|
||||||
|
- reg: Custom address of EEPROM device (If not specified IDT 89HPESx
|
||||||
|
(optional) device will try to communicate with EEPROM sited by default
|
||||||
|
address - 0x50)
|
||||||
|
- read-only : Parameterless property disables writes to the EEPROM
|
||||||
|
(optional)
|
||||||
|
|
||||||
|
Example:
|
||||||
|
idt@60 {
|
||||||
|
compatible = "idt,89hpes32nt8ag2";
|
||||||
|
reg = <0x74>;
|
||||||
|
#address-cells = <1>;
|
||||||
|
#size-cells = <0>;
|
||||||
|
|
||||||
|
eeprom@50 {
|
||||||
|
compatible = "onsemi,24c64";
|
||||||
|
reg = <0x50>;
|
||||||
|
read-only;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
Freescale i.MX6 On-Chip OTP Controller (OCOTP) device tree bindings
|
Freescale i.MX6 On-Chip OTP Controller (OCOTP) device tree bindings
|
||||||
|
|
||||||
This binding represents the on-chip eFuse OTP controller found on
|
This binding represents the on-chip eFuse OTP controller found on
|
||||||
i.MX6Q/D, i.MX6DL/S, i.MX6SL, and i.MX6SX SoCs.
|
i.MX6Q/D, i.MX6DL/S, i.MX6SL, i.MX6SX and i.MX6UL SoCs.
|
||||||
|
|
||||||
Required properties:
|
Required properties:
|
||||||
- compatible: should be one of
|
- compatible: should be one of
|
||||||
"fsl,imx6q-ocotp" (i.MX6Q/D/DL/S),
|
"fsl,imx6q-ocotp" (i.MX6Q/D/DL/S),
|
||||||
"fsl,imx6sl-ocotp" (i.MX6SL), or
|
"fsl,imx6sl-ocotp" (i.MX6SL), or
|
||||||
"fsl,imx6sx-ocotp" (i.MX6SX), followed by "syscon".
|
"fsl,imx6sx-ocotp" (i.MX6SX),
|
||||||
|
"fsl,imx6ul-ocotp" (i.MX6UL),
|
||||||
|
followed by "syscon".
|
||||||
- reg: Should contain the register base and length.
|
- reg: Should contain the register base and length.
|
||||||
- clocks: Should contain a phandle pointing to the gated peripheral clock.
|
- clocks: Should contain a phandle pointing to the gated peripheral clock.
|
||||||
|
|
||||||
|
|
|
@ -42,6 +42,12 @@ Optional properties in the area nodes:
|
||||||
and in use by another device or devices
|
and in use by another device or devices
|
||||||
- export : indicates that the reserved SRAM area may be accessed outside
|
- export : indicates that the reserved SRAM area may be accessed outside
|
||||||
of the kernel, e.g. by bootloader or userspace
|
of the kernel, e.g. by bootloader or userspace
|
||||||
|
- protect-exec : Same as 'pool' above but with the additional
|
||||||
|
constraint that code wil be run from the region and
|
||||||
|
that the memory is maintained as read-only, executable
|
||||||
|
during code execution. NOTE: This region must be page
|
||||||
|
aligned on start and end in order to properly allow
|
||||||
|
manipulation of the page attributes.
|
||||||
- label : the name for the reserved partition, if omitted, the label
|
- label : the name for the reserved partition, if omitted, the label
|
||||||
is taken from the node name excluding the unit address.
|
is taken from the node name excluding the unit address.
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,7 @@ available subsections can be seen below.
|
||||||
miscellaneous
|
miscellaneous
|
||||||
vme
|
vme
|
||||||
80211/index
|
80211/index
|
||||||
|
uio-howto
|
||||||
|
|
||||||
.. only:: subproject and html
|
.. only:: subproject and html
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,705 @@
|
||||||
|
=======================
|
||||||
|
The Userspace I/O HOWTO
|
||||||
|
=======================
|
||||||
|
|
||||||
|
:Author: Hans-Jürgen Koch Linux developer, Linutronix
|
||||||
|
:Date: 2006-12-11
|
||||||
|
|
||||||
|
About this document
|
||||||
|
===================
|
||||||
|
|
||||||
|
Translations
|
||||||
|
------------
|
||||||
|
|
||||||
|
If you know of any translations for this document, or you are interested
|
||||||
|
in translating it, please email me hjk@hansjkoch.de.
|
||||||
|
|
||||||
|
Preface
|
||||||
|
-------
|
||||||
|
|
||||||
|
For many types of devices, creating a Linux kernel driver is overkill.
|
||||||
|
All that is really needed is some way to handle an interrupt and provide
|
||||||
|
access to the memory space of the device. The logic of controlling the
|
||||||
|
device does not necessarily have to be within the kernel, as the device
|
||||||
|
does not need to take advantage of any of other resources that the
|
||||||
|
kernel provides. One such common class of devices that are like this are
|
||||||
|
for industrial I/O cards.
|
||||||
|
|
||||||
|
To address this situation, the userspace I/O system (UIO) was designed.
|
||||||
|
For typical industrial I/O cards, only a very small kernel module is
|
||||||
|
needed. The main part of the driver will run in user space. This
|
||||||
|
simplifies development and reduces the risk of serious bugs within a
|
||||||
|
kernel module.
|
||||||
|
|
||||||
|
Please note that UIO is not an universal driver interface. Devices that
|
||||||
|
are already handled well by other kernel subsystems (like networking or
|
||||||
|
serial or USB) are no candidates for an UIO driver. Hardware that is
|
||||||
|
ideally suited for an UIO driver fulfills all of the following:
|
||||||
|
|
||||||
|
- The device has memory that can be mapped. The device can be
|
||||||
|
controlled completely by writing to this memory.
|
||||||
|
|
||||||
|
- The device usually generates interrupts.
|
||||||
|
|
||||||
|
- The device does not fit into one of the standard kernel subsystems.
|
||||||
|
|
||||||
|
Acknowledgments
|
||||||
|
---------------
|
||||||
|
|
||||||
|
I'd like to thank Thomas Gleixner and Benedikt Spranger of Linutronix,
|
||||||
|
who have not only written most of the UIO code, but also helped greatly
|
||||||
|
writing this HOWTO by giving me all kinds of background information.
|
||||||
|
|
||||||
|
Feedback
|
||||||
|
--------
|
||||||
|
|
||||||
|
Find something wrong with this document? (Or perhaps something right?) I
|
||||||
|
would love to hear from you. Please email me at hjk@hansjkoch.de.
|
||||||
|
|
||||||
|
About UIO
|
||||||
|
=========
|
||||||
|
|
||||||
|
If you use UIO for your card's driver, here's what you get:
|
||||||
|
|
||||||
|
- only one small kernel module to write and maintain.
|
||||||
|
|
||||||
|
- develop the main part of your driver in user space, with all the
|
||||||
|
tools and libraries you're used to.
|
||||||
|
|
||||||
|
- bugs in your driver won't crash the kernel.
|
||||||
|
|
||||||
|
- updates of your driver can take place without recompiling the kernel.
|
||||||
|
|
||||||
|
How UIO works
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Each UIO device is accessed through a device file and several sysfs
|
||||||
|
attribute files. The device file will be called ``/dev/uio0`` for the
|
||||||
|
first device, and ``/dev/uio1``, ``/dev/uio2`` and so on for subsequent
|
||||||
|
devices.
|
||||||
|
|
||||||
|
``/dev/uioX`` is used to access the address space of the card. Just use
|
||||||
|
:c:func:`mmap()` to access registers or RAM locations of your card.
|
||||||
|
|
||||||
|
Interrupts are handled by reading from ``/dev/uioX``. A blocking
|
||||||
|
:c:func:`read()` from ``/dev/uioX`` will return as soon as an
|
||||||
|
interrupt occurs. You can also use :c:func:`select()` on
|
||||||
|
``/dev/uioX`` to wait for an interrupt. The integer value read from
|
||||||
|
``/dev/uioX`` represents the total interrupt count. You can use this
|
||||||
|
number to figure out if you missed some interrupts.
|
||||||
|
|
||||||
|
For some hardware that has more than one interrupt source internally,
|
||||||
|
but not separate IRQ mask and status registers, there might be
|
||||||
|
situations where userspace cannot determine what the interrupt source
|
||||||
|
was if the kernel handler disables them by writing to the chip's IRQ
|
||||||
|
register. In such a case, the kernel has to disable the IRQ completely
|
||||||
|
to leave the chip's register untouched. Now the userspace part can
|
||||||
|
determine the cause of the interrupt, but it cannot re-enable
|
||||||
|
interrupts. Another cornercase is chips where re-enabling interrupts is
|
||||||
|
a read-modify-write operation to a combined IRQ status/acknowledge
|
||||||
|
register. This would be racy if a new interrupt occurred simultaneously.
|
||||||
|
|
||||||
|
To address these problems, UIO also implements a write() function. It is
|
||||||
|
normally not used and can be ignored for hardware that has only a single
|
||||||
|
interrupt source or has separate IRQ mask and status registers. If you
|
||||||
|
need it, however, a write to ``/dev/uioX`` will call the
|
||||||
|
:c:func:`irqcontrol()` function implemented by the driver. You have
|
||||||
|
to write a 32-bit value that is usually either 0 or 1 to disable or
|
||||||
|
enable interrupts. If a driver does not implement
|
||||||
|
:c:func:`irqcontrol()`, :c:func:`write()` will return with
|
||||||
|
``-ENOSYS``.
|
||||||
|
|
||||||
|
To handle interrupts properly, your custom kernel module can provide its
|
||||||
|
own interrupt handler. It will automatically be called by the built-in
|
||||||
|
handler.
|
||||||
|
|
||||||
|
For cards that don't generate interrupts but need to be polled, there is
|
||||||
|
the possibility to set up a timer that triggers the interrupt handler at
|
||||||
|
configurable time intervals. This interrupt simulation is done by
|
||||||
|
calling :c:func:`uio_event_notify()` from the timer's event
|
||||||
|
handler.
|
||||||
|
|
||||||
|
Each driver provides attributes that are used to read or write
|
||||||
|
variables. These attributes are accessible through sysfs files. A custom
|
||||||
|
kernel driver module can add its own attributes to the device owned by
|
||||||
|
the uio driver, but not added to the UIO device itself at this time.
|
||||||
|
This might change in the future if it would be found to be useful.
|
||||||
|
|
||||||
|
The following standard attributes are provided by the UIO framework:
|
||||||
|
|
||||||
|
- ``name``: The name of your device. It is recommended to use the name
|
||||||
|
of your kernel module for this.
|
||||||
|
|
||||||
|
- ``version``: A version string defined by your driver. This allows the
|
||||||
|
user space part of your driver to deal with different versions of the
|
||||||
|
kernel module.
|
||||||
|
|
||||||
|
- ``event``: The total number of interrupts handled by the driver since
|
||||||
|
the last time the device node was read.
|
||||||
|
|
||||||
|
These attributes appear under the ``/sys/class/uio/uioX`` directory.
|
||||||
|
Please note that this directory might be a symlink, and not a real
|
||||||
|
directory. Any userspace code that accesses it must be able to handle
|
||||||
|
this.
|
||||||
|
|
||||||
|
Each UIO device can make one or more memory regions available for memory
|
||||||
|
mapping. This is necessary because some industrial I/O cards require
|
||||||
|
access to more than one PCI memory region in a driver.
|
||||||
|
|
||||||
|
Each mapping has its own directory in sysfs, the first mapping appears
|
||||||
|
as ``/sys/class/uio/uioX/maps/map0/``. Subsequent mappings create
|
||||||
|
directories ``map1/``, ``map2/``, and so on. These directories will only
|
||||||
|
appear if the size of the mapping is not 0.
|
||||||
|
|
||||||
|
Each ``mapX/`` directory contains four read-only files that show
|
||||||
|
attributes of the memory:
|
||||||
|
|
||||||
|
- ``name``: A string identifier for this mapping. This is optional, the
|
||||||
|
string can be empty. Drivers can set this to make it easier for
|
||||||
|
userspace to find the correct mapping.
|
||||||
|
|
||||||
|
- ``addr``: The address of memory that can be mapped.
|
||||||
|
|
||||||
|
- ``size``: The size, in bytes, of the memory pointed to by addr.
|
||||||
|
|
||||||
|
- ``offset``: The offset, in bytes, that has to be added to the pointer
|
||||||
|
returned by :c:func:`mmap()` to get to the actual device memory.
|
||||||
|
This is important if the device's memory is not page aligned.
|
||||||
|
Remember that pointers returned by :c:func:`mmap()` are always
|
||||||
|
page aligned, so it is good style to always add this offset.
|
||||||
|
|
||||||
|
From userspace, the different mappings are distinguished by adjusting
|
||||||
|
the ``offset`` parameter of the :c:func:`mmap()` call. To map the
|
||||||
|
memory of mapping N, you have to use N times the page size as your
|
||||||
|
offset::
|
||||||
|
|
||||||
|
offset = N * getpagesize();
|
||||||
|
|
||||||
|
Sometimes there is hardware with memory-like regions that can not be
|
||||||
|
mapped with the technique described here, but there are still ways to
|
||||||
|
access them from userspace. The most common example are x86 ioports. On
|
||||||
|
x86 systems, userspace can access these ioports using
|
||||||
|
:c:func:`ioperm()`, :c:func:`iopl()`, :c:func:`inb()`,
|
||||||
|
:c:func:`outb()`, and similar functions.
|
||||||
|
|
||||||
|
Since these ioport regions can not be mapped, they will not appear under
|
||||||
|
``/sys/class/uio/uioX/maps/`` like the normal memory described above.
|
||||||
|
Without information about the port regions a hardware has to offer, it
|
||||||
|
becomes difficult for the userspace part of the driver to find out which
|
||||||
|
ports belong to which UIO device.
|
||||||
|
|
||||||
|
To address this situation, the new directory
|
||||||
|
``/sys/class/uio/uioX/portio/`` was added. It only exists if the driver
|
||||||
|
wants to pass information about one or more port regions to userspace.
|
||||||
|
If that is the case, subdirectories named ``port0``, ``port1``, and so
|
||||||
|
on, will appear underneath ``/sys/class/uio/uioX/portio/``.
|
||||||
|
|
||||||
|
Each ``portX/`` directory contains four read-only files that show name,
|
||||||
|
start, size, and type of the port region:
|
||||||
|
|
||||||
|
- ``name``: A string identifier for this port region. The string is
|
||||||
|
optional and can be empty. Drivers can set it to make it easier for
|
||||||
|
userspace to find a certain port region.
|
||||||
|
|
||||||
|
- ``start``: The first port of this region.
|
||||||
|
|
||||||
|
- ``size``: The number of ports in this region.
|
||||||
|
|
||||||
|
- ``porttype``: A string describing the type of port.
|
||||||
|
|
||||||
|
Writing your own kernel module
|
||||||
|
==============================
|
||||||
|
|
||||||
|
Please have a look at ``uio_cif.c`` as an example. The following
|
||||||
|
paragraphs explain the different sections of this file.
|
||||||
|
|
||||||
|
struct uio_info
|
||||||
|
---------------
|
||||||
|
|
||||||
|
This structure tells the framework the details of your driver, Some of
|
||||||
|
the members are required, others are optional.
|
||||||
|
|
||||||
|
- ``const char *name``: Required. The name of your driver as it will
|
||||||
|
appear in sysfs. I recommend using the name of your module for this.
|
||||||
|
|
||||||
|
- ``const char *version``: Required. This string appears in
|
||||||
|
``/sys/class/uio/uioX/version``.
|
||||||
|
|
||||||
|
- ``struct uio_mem mem[ MAX_UIO_MAPS ]``: Required if you have memory
|
||||||
|
that can be mapped with :c:func:`mmap()`. For each mapping you
|
||||||
|
need to fill one of the ``uio_mem`` structures. See the description
|
||||||
|
below for details.
|
||||||
|
|
||||||
|
- ``struct uio_port port[ MAX_UIO_PORTS_REGIONS ]``: Required if you
|
||||||
|
want to pass information about ioports to userspace. For each port
|
||||||
|
region you need to fill one of the ``uio_port`` structures. See the
|
||||||
|
description below for details.
|
||||||
|
|
||||||
|
- ``long irq``: Required. If your hardware generates an interrupt, it's
|
||||||
|
your modules task to determine the irq number during initialization.
|
||||||
|
If you don't have a hardware generated interrupt but want to trigger
|
||||||
|
the interrupt handler in some other way, set ``irq`` to
|
||||||
|
``UIO_IRQ_CUSTOM``. If you had no interrupt at all, you could set
|
||||||
|
``irq`` to ``UIO_IRQ_NONE``, though this rarely makes sense.
|
||||||
|
|
||||||
|
- ``unsigned long irq_flags``: Required if you've set ``irq`` to a
|
||||||
|
hardware interrupt number. The flags given here will be used in the
|
||||||
|
call to :c:func:`request_irq()`.
|
||||||
|
|
||||||
|
- ``int (*mmap)(struct uio_info *info, struct vm_area_struct *vma)``:
|
||||||
|
Optional. If you need a special :c:func:`mmap()`
|
||||||
|
function, you can set it here. If this pointer is not NULL, your
|
||||||
|
:c:func:`mmap()` will be called instead of the built-in one.
|
||||||
|
|
||||||
|
- ``int (*open)(struct uio_info *info, struct inode *inode)``:
|
||||||
|
Optional. You might want to have your own :c:func:`open()`,
|
||||||
|
e.g. to enable interrupts only when your device is actually used.
|
||||||
|
|
||||||
|
- ``int (*release)(struct uio_info *info, struct inode *inode)``:
|
||||||
|
Optional. If you define your own :c:func:`open()`, you will
|
||||||
|
probably also want a custom :c:func:`release()` function.
|
||||||
|
|
||||||
|
- ``int (*irqcontrol)(struct uio_info *info, s32 irq_on)``:
|
||||||
|
Optional. If you need to be able to enable or disable interrupts
|
||||||
|
from userspace by writing to ``/dev/uioX``, you can implement this
|
||||||
|
function. The parameter ``irq_on`` will be 0 to disable interrupts
|
||||||
|
and 1 to enable them.
|
||||||
|
|
||||||
|
Usually, your device will have one or more memory regions that can be
|
||||||
|
mapped to user space. For each region, you have to set up a
|
||||||
|
``struct uio_mem`` in the ``mem[]`` array. Here's a description of the
|
||||||
|
fields of ``struct uio_mem``:
|
||||||
|
|
||||||
|
- ``const char *name``: Optional. Set this to help identify the memory
|
||||||
|
region, it will show up in the corresponding sysfs node.
|
||||||
|
|
||||||
|
- ``int memtype``: Required if the mapping is used. Set this to
|
||||||
|
``UIO_MEM_PHYS`` if you you have physical memory on your card to be
|
||||||
|
mapped. Use ``UIO_MEM_LOGICAL`` for logical memory (e.g. allocated
|
||||||
|
with :c:func:`kmalloc()`). There's also ``UIO_MEM_VIRTUAL`` for
|
||||||
|
virtual memory.
|
||||||
|
|
||||||
|
- ``phys_addr_t addr``: Required if the mapping is used. Fill in the
|
||||||
|
address of your memory block. This address is the one that appears in
|
||||||
|
sysfs.
|
||||||
|
|
||||||
|
- ``resource_size_t size``: Fill in the size of the memory block that
|
||||||
|
``addr`` points to. If ``size`` is zero, the mapping is considered
|
||||||
|
unused. Note that you *must* initialize ``size`` with zero for all
|
||||||
|
unused mappings.
|
||||||
|
|
||||||
|
- ``void *internal_addr``: If you have to access this memory region
|
||||||
|
from within your kernel module, you will want to map it internally by
|
||||||
|
using something like :c:func:`ioremap()`. Addresses returned by
|
||||||
|
this function cannot be mapped to user space, so you must not store
|
||||||
|
it in ``addr``. Use ``internal_addr`` instead to remember such an
|
||||||
|
address.
|
||||||
|
|
||||||
|
Please do not touch the ``map`` element of ``struct uio_mem``! It is
|
||||||
|
used by the UIO framework to set up sysfs files for this mapping. Simply
|
||||||
|
leave it alone.
|
||||||
|
|
||||||
|
Sometimes, your device can have one or more port regions which can not
|
||||||
|
be mapped to userspace. But if there are other possibilities for
|
||||||
|
userspace to access these ports, it makes sense to make information
|
||||||
|
about the ports available in sysfs. For each region, you have to set up
|
||||||
|
a ``struct uio_port`` in the ``port[]`` array. Here's a description of
|
||||||
|
the fields of ``struct uio_port``:
|
||||||
|
|
||||||
|
- ``char *porttype``: Required. Set this to one of the predefined
|
||||||
|
constants. Use ``UIO_PORT_X86`` for the ioports found in x86
|
||||||
|
architectures.
|
||||||
|
|
||||||
|
- ``unsigned long start``: Required if the port region is used. Fill in
|
||||||
|
the number of the first port of this region.
|
||||||
|
|
||||||
|
- ``unsigned long size``: Fill in the number of ports in this region.
|
||||||
|
If ``size`` is zero, the region is considered unused. Note that you
|
||||||
|
*must* initialize ``size`` with zero for all unused regions.
|
||||||
|
|
||||||
|
Please do not touch the ``portio`` element of ``struct uio_port``! It is
|
||||||
|
used internally by the UIO framework to set up sysfs files for this
|
||||||
|
region. Simply leave it alone.
|
||||||
|
|
||||||
|
Adding an interrupt handler
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
What you need to do in your interrupt handler depends on your hardware
|
||||||
|
and on how you want to handle it. You should try to keep the amount of
|
||||||
|
code in your kernel interrupt handler low. If your hardware requires no
|
||||||
|
action that you *have* to perform after each interrupt, then your
|
||||||
|
handler can be empty.
|
||||||
|
|
||||||
|
If, on the other hand, your hardware *needs* some action to be performed
|
||||||
|
after each interrupt, then you *must* do it in your kernel module. Note
|
||||||
|
that you cannot rely on the userspace part of your driver. Your
|
||||||
|
userspace program can terminate at any time, possibly leaving your
|
||||||
|
hardware in a state where proper interrupt handling is still required.
|
||||||
|
|
||||||
|
There might also be applications where you want to read data from your
|
||||||
|
hardware at each interrupt and buffer it in a piece of kernel memory
|
||||||
|
you've allocated for that purpose. With this technique you could avoid
|
||||||
|
loss of data if your userspace program misses an interrupt.
|
||||||
|
|
||||||
|
A note on shared interrupts: Your driver should support interrupt
|
||||||
|
sharing whenever this is possible. It is possible if and only if your
|
||||||
|
driver can detect whether your hardware has triggered the interrupt or
|
||||||
|
not. This is usually done by looking at an interrupt status register. If
|
||||||
|
your driver sees that the IRQ bit is actually set, it will perform its
|
||||||
|
actions, and the handler returns IRQ_HANDLED. If the driver detects
|
||||||
|
that it was not your hardware that caused the interrupt, it will do
|
||||||
|
nothing and return IRQ_NONE, allowing the kernel to call the next
|
||||||
|
possible interrupt handler.
|
||||||
|
|
||||||
|
If you decide not to support shared interrupts, your card won't work in
|
||||||
|
computers with no free interrupts. As this frequently happens on the PC
|
||||||
|
platform, you can save yourself a lot of trouble by supporting interrupt
|
||||||
|
sharing.
|
||||||
|
|
||||||
|
Using uio_pdrv for platform devices
|
||||||
|
-----------------------------------
|
||||||
|
|
||||||
|
In many cases, UIO drivers for platform devices can be handled in a
|
||||||
|
generic way. In the same place where you define your
|
||||||
|
``struct platform_device``, you simply also implement your interrupt
|
||||||
|
handler and fill your ``struct uio_info``. A pointer to this
|
||||||
|
``struct uio_info`` is then used as ``platform_data`` for your platform
|
||||||
|
device.
|
||||||
|
|
||||||
|
You also need to set up an array of ``struct resource`` containing
|
||||||
|
addresses and sizes of your memory mappings. This information is passed
|
||||||
|
to the driver using the ``.resource`` and ``.num_resources`` elements of
|
||||||
|
``struct platform_device``.
|
||||||
|
|
||||||
|
You now have to set the ``.name`` element of ``struct platform_device``
|
||||||
|
to ``"uio_pdrv"`` to use the generic UIO platform device driver. This
|
||||||
|
driver will fill the ``mem[]`` array according to the resources given,
|
||||||
|
and register the device.
|
||||||
|
|
||||||
|
The advantage of this approach is that you only have to edit a file you
|
||||||
|
need to edit anyway. You do not have to create an extra driver.
|
||||||
|
|
||||||
|
Using uio_pdrv_genirq for platform devices
|
||||||
|
------------------------------------------
|
||||||
|
|
||||||
|
Especially in embedded devices, you frequently find chips where the irq
|
||||||
|
pin is tied to its own dedicated interrupt line. In such cases, where
|
||||||
|
you can be really sure the interrupt is not shared, we can take the
|
||||||
|
concept of ``uio_pdrv`` one step further and use a generic interrupt
|
||||||
|
handler. That's what ``uio_pdrv_genirq`` does.
|
||||||
|
|
||||||
|
The setup for this driver is the same as described above for
|
||||||
|
``uio_pdrv``, except that you do not implement an interrupt handler. The
|
||||||
|
``.handler`` element of ``struct uio_info`` must remain ``NULL``. The
|
||||||
|
``.irq_flags`` element must not contain ``IRQF_SHARED``.
|
||||||
|
|
||||||
|
You will set the ``.name`` element of ``struct platform_device`` to
|
||||||
|
``"uio_pdrv_genirq"`` to use this driver.
|
||||||
|
|
||||||
|
The generic interrupt handler of ``uio_pdrv_genirq`` will simply disable
|
||||||
|
the interrupt line using :c:func:`disable_irq_nosync()`. After
|
||||||
|
doing its work, userspace can reenable the interrupt by writing
|
||||||
|
0x00000001 to the UIO device file. The driver already implements an
|
||||||
|
:c:func:`irq_control()` to make this possible, you must not
|
||||||
|
implement your own.
|
||||||
|
|
||||||
|
Using ``uio_pdrv_genirq`` not only saves a few lines of interrupt
|
||||||
|
handler code. You also do not need to know anything about the chip's
|
||||||
|
internal registers to create the kernel part of the driver. All you need
|
||||||
|
to know is the irq number of the pin the chip is connected to.
|
||||||
|
|
||||||
|
Using uio_dmem_genirq for platform devices
|
||||||
|
------------------------------------------
|
||||||
|
|
||||||
|
In addition to statically allocated memory ranges, they may also be a
|
||||||
|
desire to use dynamically allocated regions in a user space driver. In
|
||||||
|
particular, being able to access memory made available through the
|
||||||
|
dma-mapping API, may be particularly useful. The ``uio_dmem_genirq``
|
||||||
|
driver provides a way to accomplish this.
|
||||||
|
|
||||||
|
This driver is used in a similar manner to the ``"uio_pdrv_genirq"``
|
||||||
|
driver with respect to interrupt configuration and handling.
|
||||||
|
|
||||||
|
Set the ``.name`` element of ``struct platform_device`` to
|
||||||
|
``"uio_dmem_genirq"`` to use this driver.
|
||||||
|
|
||||||
|
When using this driver, fill in the ``.platform_data`` element of
|
||||||
|
``struct platform_device``, which is of type
|
||||||
|
``struct uio_dmem_genirq_pdata`` and which contains the following
|
||||||
|
elements:
|
||||||
|
|
||||||
|
- ``struct uio_info uioinfo``: The same structure used as the
|
||||||
|
``uio_pdrv_genirq`` platform data
|
||||||
|
|
||||||
|
- ``unsigned int *dynamic_region_sizes``: Pointer to list of sizes of
|
||||||
|
dynamic memory regions to be mapped into user space.
|
||||||
|
|
||||||
|
- ``unsigned int num_dynamic_regions``: Number of elements in
|
||||||
|
``dynamic_region_sizes`` array.
|
||||||
|
|
||||||
|
The dynamic regions defined in the platform data will be appended to the
|
||||||
|
`` mem[] `` array after the platform device resources, which implies
|
||||||
|
that the total number of static and dynamic memory regions cannot exceed
|
||||||
|
``MAX_UIO_MAPS``.
|
||||||
|
|
||||||
|
The dynamic memory regions will be allocated when the UIO device file,
|
||||||
|
``/dev/uioX`` is opened. Similar to static memory resources, the memory
|
||||||
|
region information for dynamic regions is then visible via sysfs at
|
||||||
|
``/sys/class/uio/uioX/maps/mapY/*``. The dynamic memory regions will be
|
||||||
|
freed when the UIO device file is closed. When no processes are holding
|
||||||
|
the device file open, the address returned to userspace is ~0.
|
||||||
|
|
||||||
|
Writing a driver in userspace
|
||||||
|
=============================
|
||||||
|
|
||||||
|
Once you have a working kernel module for your hardware, you can write
|
||||||
|
the userspace part of your driver. You don't need any special libraries,
|
||||||
|
your driver can be written in any reasonable language, you can use
|
||||||
|
floating point numbers and so on. In short, you can use all the tools
|
||||||
|
and libraries you'd normally use for writing a userspace application.
|
||||||
|
|
||||||
|
Getting information about your UIO device
|
||||||
|
-----------------------------------------
|
||||||
|
|
||||||
|
Information about all UIO devices is available in sysfs. The first thing
|
||||||
|
you should do in your driver is check ``name`` and ``version`` to make
|
||||||
|
sure your talking to the right device and that its kernel driver has the
|
||||||
|
version you expect.
|
||||||
|
|
||||||
|
You should also make sure that the memory mapping you need exists and
|
||||||
|
has the size you expect.
|
||||||
|
|
||||||
|
There is a tool called ``lsuio`` that lists UIO devices and their
|
||||||
|
attributes. It is available here:
|
||||||
|
|
||||||
|
http://www.osadl.org/projects/downloads/UIO/user/
|
||||||
|
|
||||||
|
With ``lsuio`` you can quickly check if your kernel module is loaded and
|
||||||
|
which attributes it exports. Have a look at the manpage for details.
|
||||||
|
|
||||||
|
The source code of ``lsuio`` can serve as an example for getting
|
||||||
|
information about an UIO device. The file ``uio_helper.c`` contains a
|
||||||
|
lot of functions you could use in your userspace driver code.
|
||||||
|
|
||||||
|
mmap() device memory
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
After you made sure you've got the right device with the memory mappings
|
||||||
|
you need, all you have to do is to call :c:func:`mmap()` to map the
|
||||||
|
device's memory to userspace.
|
||||||
|
|
||||||
|
The parameter ``offset`` of the :c:func:`mmap()` call has a special
|
||||||
|
meaning for UIO devices: It is used to select which mapping of your
|
||||||
|
device you want to map. To map the memory of mapping N, you have to use
|
||||||
|
N times the page size as your offset::
|
||||||
|
|
||||||
|
offset = N * getpagesize();
|
||||||
|
|
||||||
|
N starts from zero, so if you've got only one memory range to map, set
|
||||||
|
``offset = 0``. A drawback of this technique is that memory is always
|
||||||
|
mapped beginning with its start address.
|
||||||
|
|
||||||
|
Waiting for interrupts
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
After you successfully mapped your devices memory, you can access it
|
||||||
|
like an ordinary array. Usually, you will perform some initialization.
|
||||||
|
After that, your hardware starts working and will generate an interrupt
|
||||||
|
as soon as it's finished, has some data available, or needs your
|
||||||
|
attention because an error occurred.
|
||||||
|
|
||||||
|
``/dev/uioX`` is a read-only file. A :c:func:`read()` will always
|
||||||
|
block until an interrupt occurs. There is only one legal value for the
|
||||||
|
``count`` parameter of :c:func:`read()`, and that is the size of a
|
||||||
|
signed 32 bit integer (4). Any other value for ``count`` causes
|
||||||
|
:c:func:`read()` to fail. The signed 32 bit integer read is the
|
||||||
|
interrupt count of your device. If the value is one more than the value
|
||||||
|
you read the last time, everything is OK. If the difference is greater
|
||||||
|
than one, you missed interrupts.
|
||||||
|
|
||||||
|
You can also use :c:func:`select()` on ``/dev/uioX``.
|
||||||
|
|
||||||
|
Generic PCI UIO driver
|
||||||
|
======================
|
||||||
|
|
||||||
|
The generic driver is a kernel module named uio_pci_generic. It can
|
||||||
|
work with any device compliant to PCI 2.3 (circa 2002) and any compliant
|
||||||
|
PCI Express device. Using this, you only need to write the userspace
|
||||||
|
driver, removing the need to write a hardware-specific kernel module.
|
||||||
|
|
||||||
|
Making the driver recognize the device
|
||||||
|
--------------------------------------
|
||||||
|
|
||||||
|
Since the driver does not declare any device ids, it will not get loaded
|
||||||
|
automatically and will not automatically bind to any devices, you must
|
||||||
|
load it and allocate id to the driver yourself. For example::
|
||||||
|
|
||||||
|
modprobe uio_pci_generic
|
||||||
|
echo "8086 10f5" > /sys/bus/pci/drivers/uio_pci_generic/new_id
|
||||||
|
|
||||||
|
If there already is a hardware specific kernel driver for your device,
|
||||||
|
the generic driver still won't bind to it, in this case if you want to
|
||||||
|
use the generic driver (why would you?) you'll have to manually unbind
|
||||||
|
the hardware specific driver and bind the generic driver, like this::
|
||||||
|
|
||||||
|
echo -n 0000:00:19.0 > /sys/bus/pci/drivers/e1000e/unbind
|
||||||
|
echo -n 0000:00:19.0 > /sys/bus/pci/drivers/uio_pci_generic/bind
|
||||||
|
|
||||||
|
You can verify that the device has been bound to the driver by looking
|
||||||
|
for it in sysfs, for example like the following::
|
||||||
|
|
||||||
|
ls -l /sys/bus/pci/devices/0000:00:19.0/driver
|
||||||
|
|
||||||
|
Which if successful should print::
|
||||||
|
|
||||||
|
.../0000:00:19.0/driver -> ../../../bus/pci/drivers/uio_pci_generic
|
||||||
|
|
||||||
|
Note that the generic driver will not bind to old PCI 2.2 devices. If
|
||||||
|
binding the device failed, run the following command::
|
||||||
|
|
||||||
|
dmesg
|
||||||
|
|
||||||
|
and look in the output for failure reasons.
|
||||||
|
|
||||||
|
Things to know about uio_pci_generic
|
||||||
|
------------------------------------
|
||||||
|
|
||||||
|
Interrupts are handled using the Interrupt Disable bit in the PCI
|
||||||
|
command register and Interrupt Status bit in the PCI status register.
|
||||||
|
All devices compliant to PCI 2.3 (circa 2002) and all compliant PCI
|
||||||
|
Express devices should support these bits. uio_pci_generic detects
|
||||||
|
this support, and won't bind to devices which do not support the
|
||||||
|
Interrupt Disable Bit in the command register.
|
||||||
|
|
||||||
|
On each interrupt, uio_pci_generic sets the Interrupt Disable bit.
|
||||||
|
This prevents the device from generating further interrupts until the
|
||||||
|
bit is cleared. The userspace driver should clear this bit before
|
||||||
|
blocking and waiting for more interrupts.
|
||||||
|
|
||||||
|
Writing userspace driver using uio_pci_generic
|
||||||
|
------------------------------------------------
|
||||||
|
|
||||||
|
Userspace driver can use pci sysfs interface, or the libpci library that
|
||||||
|
wraps it, to talk to the device and to re-enable interrupts by writing
|
||||||
|
to the command register.
|
||||||
|
|
||||||
|
Example code using uio_pci_generic
|
||||||
|
----------------------------------
|
||||||
|
|
||||||
|
Here is some sample userspace driver code using uio_pci_generic::
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
int uiofd;
|
||||||
|
int configfd;
|
||||||
|
int err;
|
||||||
|
int i;
|
||||||
|
unsigned icount;
|
||||||
|
unsigned char command_high;
|
||||||
|
|
||||||
|
uiofd = open("/dev/uio0", O_RDONLY);
|
||||||
|
if (uiofd < 0) {
|
||||||
|
perror("uio open:");
|
||||||
|
return errno;
|
||||||
|
}
|
||||||
|
configfd = open("/sys/class/uio/uio0/device/config", O_RDWR);
|
||||||
|
if (configfd < 0) {
|
||||||
|
perror("config open:");
|
||||||
|
return errno;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read and cache command value */
|
||||||
|
err = pread(configfd, &command_high, 1, 5);
|
||||||
|
if (err != 1) {
|
||||||
|
perror("command config read:");
|
||||||
|
return errno;
|
||||||
|
}
|
||||||
|
command_high &= ~0x4;
|
||||||
|
|
||||||
|
for(i = 0;; ++i) {
|
||||||
|
/* Print out a message, for debugging. */
|
||||||
|
if (i == 0)
|
||||||
|
fprintf(stderr, "Started uio test driver.\n");
|
||||||
|
else
|
||||||
|
fprintf(stderr, "Interrupts: %d\n", icount);
|
||||||
|
|
||||||
|
/****************************************/
|
||||||
|
/* Here we got an interrupt from the
|
||||||
|
device. Do something to it. */
|
||||||
|
/****************************************/
|
||||||
|
|
||||||
|
/* Re-enable interrupts. */
|
||||||
|
err = pwrite(configfd, &command_high, 1, 5);
|
||||||
|
if (err != 1) {
|
||||||
|
perror("config write:");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Wait for next interrupt. */
|
||||||
|
err = read(uiofd, &icount, 4);
|
||||||
|
if (err != 4) {
|
||||||
|
perror("uio read:");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return errno;
|
||||||
|
}
|
||||||
|
|
||||||
|
Generic Hyper-V UIO driver
|
||||||
|
==========================
|
||||||
|
|
||||||
|
The generic driver is a kernel module named uio_hv_generic. It
|
||||||
|
supports devices on the Hyper-V VMBus similar to uio_pci_generic on
|
||||||
|
PCI bus.
|
||||||
|
|
||||||
|
Making the driver recognize the device
|
||||||
|
--------------------------------------
|
||||||
|
|
||||||
|
Since the driver does not declare any device GUID's, it will not get
|
||||||
|
loaded automatically and will not automatically bind to any devices, you
|
||||||
|
must load it and allocate id to the driver yourself. For example, to use
|
||||||
|
the network device GUID::
|
||||||
|
|
||||||
|
modprobe uio_hv_generic
|
||||||
|
echo "f8615163-df3e-46c5-913f-f2d2f965ed0e" > /sys/bus/vmbus/drivers/uio_hv_generic/new_id
|
||||||
|
|
||||||
|
If there already is a hardware specific kernel driver for the device,
|
||||||
|
the generic driver still won't bind to it, in this case if you want to
|
||||||
|
use the generic driver (why would you?) you'll have to manually unbind
|
||||||
|
the hardware specific driver and bind the generic driver, like this::
|
||||||
|
|
||||||
|
echo -n vmbus-ed963694-e847-4b2a-85af-bc9cfc11d6f3 > /sys/bus/vmbus/drivers/hv_netvsc/unbind
|
||||||
|
echo -n vmbus-ed963694-e847-4b2a-85af-bc9cfc11d6f3 > /sys/bus/vmbus/drivers/uio_hv_generic/bind
|
||||||
|
|
||||||
|
You can verify that the device has been bound to the driver by looking
|
||||||
|
for it in sysfs, for example like the following::
|
||||||
|
|
||||||
|
ls -l /sys/bus/vmbus/devices/vmbus-ed963694-e847-4b2a-85af-bc9cfc11d6f3/driver
|
||||||
|
|
||||||
|
Which if successful should print::
|
||||||
|
|
||||||
|
.../vmbus-ed963694-e847-4b2a-85af-bc9cfc11d6f3/driver -> ../../../bus/vmbus/drivers/uio_hv_generic
|
||||||
|
|
||||||
|
Things to know about uio_hv_generic
|
||||||
|
-----------------------------------
|
||||||
|
|
||||||
|
On each interrupt, uio_hv_generic sets the Interrupt Disable bit. This
|
||||||
|
prevents the device from generating further interrupts until the bit is
|
||||||
|
cleared. The userspace driver should clear this bit before blocking and
|
||||||
|
waiting for more interrupts.
|
||||||
|
|
||||||
|
Further information
|
||||||
|
===================
|
||||||
|
|
||||||
|
- `OSADL homepage. <http://www.osadl.org>`_
|
||||||
|
|
||||||
|
- `Linutronix homepage. <http://www.linutronix.de>`_
|
|
@ -0,0 +1,22 @@
|
||||||
|
Intel INT3496 ACPI device extcon driver documentation
|
||||||
|
-----------------------------------------------------
|
||||||
|
|
||||||
|
The Intel INT3496 ACPI device extcon driver is a driver for ACPI
|
||||||
|
devices with an acpi-id of INT3496, such as found for example on
|
||||||
|
Intel Baytrail and Cherrytrail tablets.
|
||||||
|
|
||||||
|
This ACPI device describes how the OS can read the id-pin of the devices'
|
||||||
|
USB-otg port, as well as how it optionally can enable Vbus output on the
|
||||||
|
otg port and how it can optionally control the muxing of the data pins
|
||||||
|
between an USB host and an USB peripheral controller.
|
||||||
|
|
||||||
|
The ACPI devices exposes this functionality by returning an array with up
|
||||||
|
to 3 gpio descriptors from its ACPI _CRS (Current Resource Settings) call:
|
||||||
|
|
||||||
|
Index 0: The input gpio for the id-pin, this is always present and valid
|
||||||
|
Index 1: The output gpio for enabling Vbus output from the device to the otg
|
||||||
|
port, write 1 to enable the Vbus output (this gpio descriptor may
|
||||||
|
be absent or invalid)
|
||||||
|
Index 2: The output gpio for muxing of the data pins between the USB host and
|
||||||
|
the USB peripheral controller, write 1 to mux to the peripheral
|
||||||
|
controller
|
|
@ -22,7 +22,16 @@ To program the FPGA from a file or from a buffer:
|
||||||
struct fpga_image_info *info,
|
struct fpga_image_info *info,
|
||||||
const char *buf, size_t count);
|
const char *buf, size_t count);
|
||||||
|
|
||||||
Load the FPGA from an image which exists as a buffer in memory.
|
Load the FPGA from an image which exists as a contiguous buffer in
|
||||||
|
memory. Allocating contiguous kernel memory for the buffer should be avoided,
|
||||||
|
users are encouraged to use the _sg interface instead of this.
|
||||||
|
|
||||||
|
int fpga_mgr_buf_load_sg(struct fpga_manager *mgr,
|
||||||
|
struct fpga_image_info *info,
|
||||||
|
struct sg_table *sgt);
|
||||||
|
|
||||||
|
Load the FPGA from an image from non-contiguous in memory. Callers can
|
||||||
|
construct a sg_table using alloc_page backed memory.
|
||||||
|
|
||||||
int fpga_mgr_firmware_load(struct fpga_manager *mgr,
|
int fpga_mgr_firmware_load(struct fpga_manager *mgr,
|
||||||
struct fpga_image_info *info,
|
struct fpga_image_info *info,
|
||||||
|
@ -166,7 +175,7 @@ success or negative error codes otherwise.
|
||||||
|
|
||||||
The programming sequence is:
|
The programming sequence is:
|
||||||
1. .write_init
|
1. .write_init
|
||||||
2. .write (may be called once or multiple times)
|
2. .write or .write_sg (may be called once or multiple times)
|
||||||
3. .write_complete
|
3. .write_complete
|
||||||
|
|
||||||
The .write_init function will prepare the FPGA to receive the image data. The
|
The .write_init function will prepare the FPGA to receive the image data. The
|
||||||
|
@ -176,7 +185,11 @@ buffer up at least this much before starting.
|
||||||
|
|
||||||
The .write function writes a buffer to the FPGA. The buffer may be contain the
|
The .write function writes a buffer to the FPGA. The buffer may be contain the
|
||||||
whole FPGA image or may be a smaller chunk of an FPGA image. In the latter
|
whole FPGA image or may be a smaller chunk of an FPGA image. In the latter
|
||||||
case, this function is called multiple times for successive chunks.
|
case, this function is called multiple times for successive chunks. This interface
|
||||||
|
is suitable for drivers which use PIO.
|
||||||
|
|
||||||
|
The .write_sg version behaves the same as .write except the input is a sg_table
|
||||||
|
scatter list. This interface is suitable for drivers which use DMA.
|
||||||
|
|
||||||
The .write_complete function is called after all the image has been written
|
The .write_complete function is called after all the image has been written
|
||||||
to put the FPGA into operating mode.
|
to put the FPGA into operating mode.
|
||||||
|
|
|
@ -5993,6 +5993,7 @@ S: Maintained
|
||||||
F: arch/x86/include/asm/mshyperv.h
|
F: arch/x86/include/asm/mshyperv.h
|
||||||
F: arch/x86/include/uapi/asm/hyperv.h
|
F: arch/x86/include/uapi/asm/hyperv.h
|
||||||
F: arch/x86/kernel/cpu/mshyperv.c
|
F: arch/x86/kernel/cpu/mshyperv.c
|
||||||
|
F: arch/x86/hyperv
|
||||||
F: drivers/hid/hid-hyperv.c
|
F: drivers/hid/hid-hyperv.c
|
||||||
F: drivers/hv/
|
F: drivers/hv/
|
||||||
F: drivers/input/serio/hyperv-keyboard.c
|
F: drivers/input/serio/hyperv-keyboard.c
|
||||||
|
@ -13071,7 +13072,7 @@ USERSPACE I/O (UIO)
|
||||||
M: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
M: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
T: git git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc.git
|
T: git git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc.git
|
||||||
F: Documentation/DocBook/uio-howto.tmpl
|
F: Documentation/driver-api/uio-howto.rst
|
||||||
F: drivers/uio/
|
F: drivers/uio/
|
||||||
F: include/linux/uio*.h
|
F: include/linux/uio*.h
|
||||||
|
|
||||||
|
|
|
@ -557,15 +557,7 @@ static struct clk_lookup da850_clks[] = {
|
||||||
CLK("da830-mmc.0", NULL, &mmcsd0_clk),
|
CLK("da830-mmc.0", NULL, &mmcsd0_clk),
|
||||||
CLK("da830-mmc.1", NULL, &mmcsd1_clk),
|
CLK("da830-mmc.1", NULL, &mmcsd1_clk),
|
||||||
CLK("ti-aemif", NULL, &aemif_clk),
|
CLK("ti-aemif", NULL, &aemif_clk),
|
||||||
/*
|
CLK("davinci-nand.0", "aemif", &aemif_nand_clk),
|
||||||
* The only user of this clock is davinci_nand and it get's it through
|
|
||||||
* con_id. The nand node itself is created from within the aemif
|
|
||||||
* driver to guarantee that it's probed after the aemif timing
|
|
||||||
* parameters are configured. of_dev_auxdata is not accessible from
|
|
||||||
* the aemif driver and can't be passed to of_platform_populate(). For
|
|
||||||
* that reason we're leaving the dev_id here as NULL.
|
|
||||||
*/
|
|
||||||
CLK(NULL, "aemif", &aemif_nand_clk),
|
|
||||||
CLK("ohci-da8xx", "usb11", &usb11_clk),
|
CLK("ohci-da8xx", "usb11", &usb11_clk),
|
||||||
CLK("musb-da8xx", "usb20", &usb20_clk),
|
CLK("musb-da8xx", "usb20", &usb20_clk),
|
||||||
CLK("spi_davinci.0", NULL, &spi0_clk),
|
CLK("spi_davinci.0", NULL, &spi0_clk),
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#include <linux/of_irq.h>
|
#include <linux/of_irq.h>
|
||||||
#include <linux/of_platform.h>
|
#include <linux/of_platform.h>
|
||||||
#include <linux/irqdomain.h>
|
#include <linux/irqdomain.h>
|
||||||
|
#include <linux/platform_data/ti-aemif.h>
|
||||||
|
|
||||||
#include <asm/mach/arch.h>
|
#include <asm/mach/arch.h>
|
||||||
|
|
||||||
|
@ -18,6 +19,15 @@
|
||||||
#include "cp_intc.h"
|
#include "cp_intc.h"
|
||||||
#include <mach/da8xx.h>
|
#include <mach/da8xx.h>
|
||||||
|
|
||||||
|
static struct of_dev_auxdata da850_aemif_auxdata_lookup[] = {
|
||||||
|
OF_DEV_AUXDATA("ti,davinci-nand", 0x62000000, "davinci-nand.0", NULL),
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct aemif_platform_data aemif_data = {
|
||||||
|
.dev_lookup = da850_aemif_auxdata_lookup,
|
||||||
|
};
|
||||||
|
|
||||||
static struct of_dev_auxdata da850_auxdata_lookup[] __initdata = {
|
static struct of_dev_auxdata da850_auxdata_lookup[] __initdata = {
|
||||||
OF_DEV_AUXDATA("ti,davinci-i2c", 0x01c22000, "i2c_davinci.1", NULL),
|
OF_DEV_AUXDATA("ti,davinci-i2c", 0x01c22000, "i2c_davinci.1", NULL),
|
||||||
OF_DEV_AUXDATA("ti,davinci-i2c", 0x01e28000, "i2c_davinci.2", NULL),
|
OF_DEV_AUXDATA("ti,davinci-i2c", 0x01e28000, "i2c_davinci.2", NULL),
|
||||||
|
@ -37,7 +47,7 @@ static struct of_dev_auxdata da850_auxdata_lookup[] __initdata = {
|
||||||
OF_DEV_AUXDATA("ti,davinci-dm6467-emac", 0x01e20000, "davinci_emac.1",
|
OF_DEV_AUXDATA("ti,davinci-dm6467-emac", 0x01e20000, "davinci_emac.1",
|
||||||
NULL),
|
NULL),
|
||||||
OF_DEV_AUXDATA("ti,da830-mcasp-audio", 0x01d00000, "davinci-mcasp.0", NULL),
|
OF_DEV_AUXDATA("ti,da830-mcasp-audio", 0x01d00000, "davinci-mcasp.0", NULL),
|
||||||
OF_DEV_AUXDATA("ti,da850-aemif", 0x68000000, "ti-aemif", NULL),
|
OF_DEV_AUXDATA("ti,da850-aemif", 0x68000000, "ti-aemif", &aemif_data),
|
||||||
OF_DEV_AUXDATA("ti,da850-tilcdc", 0x01e13000, "da8xx_lcdc.0", NULL),
|
OF_DEV_AUXDATA("ti,da850-tilcdc", 0x01e13000, "da8xx_lcdc.0", NULL),
|
||||||
OF_DEV_AUXDATA("ti,da830-ohci", 0x01e25000, "ohci-da8xx", NULL),
|
OF_DEV_AUXDATA("ti,da830-ohci", 0x01e25000, "ohci-da8xx", NULL),
|
||||||
OF_DEV_AUXDATA("ti,da830-musb", 0x01e00000, "musb-da8xx", NULL),
|
OF_DEV_AUXDATA("ti,da830-musb", 0x01e00000, "musb-da8xx", NULL),
|
||||||
|
|
|
@ -7,6 +7,9 @@ obj-$(CONFIG_KVM) += kvm/
|
||||||
# Xen paravirtualization support
|
# Xen paravirtualization support
|
||||||
obj-$(CONFIG_XEN) += xen/
|
obj-$(CONFIG_XEN) += xen/
|
||||||
|
|
||||||
|
# Hyper-V paravirtualization support
|
||||||
|
obj-$(CONFIG_HYPERVISOR_GUEST) += hyperv/
|
||||||
|
|
||||||
# lguest paravirtualization support
|
# lguest paravirtualization support
|
||||||
obj-$(CONFIG_LGUEST_GUEST) += lguest/
|
obj-$(CONFIG_LGUEST_GUEST) += lguest/
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
obj-y := hv_init.o
|
|
@ -0,0 +1,277 @@
|
||||||
|
/*
|
||||||
|
* X86 specific Hyper-V initialization code.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2016, Microsoft, Inc.
|
||||||
|
*
|
||||||
|
* Author : K. Y. Srinivasan <kys@microsoft.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/types.h>
|
||||||
|
#include <asm/hypervisor.h>
|
||||||
|
#include <asm/hyperv.h>
|
||||||
|
#include <asm/mshyperv.h>
|
||||||
|
#include <linux/version.h>
|
||||||
|
#include <linux/vmalloc.h>
|
||||||
|
#include <linux/mm.h>
|
||||||
|
#include <linux/clockchips.h>
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef CONFIG_X86_64
|
||||||
|
|
||||||
|
static struct ms_hyperv_tsc_page *tsc_pg;
|
||||||
|
|
||||||
|
static u64 read_hv_clock_tsc(struct clocksource *arg)
|
||||||
|
{
|
||||||
|
u64 current_tick;
|
||||||
|
|
||||||
|
if (tsc_pg->tsc_sequence != 0) {
|
||||||
|
/*
|
||||||
|
* Use the tsc page to compute the value.
|
||||||
|
*/
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
u64 tmp;
|
||||||
|
u32 sequence = tsc_pg->tsc_sequence;
|
||||||
|
u64 cur_tsc;
|
||||||
|
u64 scale = tsc_pg->tsc_scale;
|
||||||
|
s64 offset = tsc_pg->tsc_offset;
|
||||||
|
|
||||||
|
rdtscll(cur_tsc);
|
||||||
|
/* current_tick = ((cur_tsc *scale) >> 64) + offset */
|
||||||
|
asm("mulq %3"
|
||||||
|
: "=d" (current_tick), "=a" (tmp)
|
||||||
|
: "a" (cur_tsc), "r" (scale));
|
||||||
|
|
||||||
|
current_tick += offset;
|
||||||
|
if (tsc_pg->tsc_sequence == sequence)
|
||||||
|
return current_tick;
|
||||||
|
|
||||||
|
if (tsc_pg->tsc_sequence != 0)
|
||||||
|
continue;
|
||||||
|
/*
|
||||||
|
* Fallback using MSR method.
|
||||||
|
*/
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rdmsrl(HV_X64_MSR_TIME_REF_COUNT, current_tick);
|
||||||
|
return current_tick;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct clocksource hyperv_cs_tsc = {
|
||||||
|
.name = "hyperv_clocksource_tsc_page",
|
||||||
|
.rating = 400,
|
||||||
|
.read = read_hv_clock_tsc,
|
||||||
|
.mask = CLOCKSOURCE_MASK(64),
|
||||||
|
.flags = CLOCK_SOURCE_IS_CONTINUOUS,
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static u64 read_hv_clock_msr(struct clocksource *arg)
|
||||||
|
{
|
||||||
|
u64 current_tick;
|
||||||
|
/*
|
||||||
|
* Read the partition counter to get the current tick count. This count
|
||||||
|
* is set to 0 when the partition is created and is incremented in
|
||||||
|
* 100 nanosecond units.
|
||||||
|
*/
|
||||||
|
rdmsrl(HV_X64_MSR_TIME_REF_COUNT, current_tick);
|
||||||
|
return current_tick;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct clocksource hyperv_cs_msr = {
|
||||||
|
.name = "hyperv_clocksource_msr",
|
||||||
|
.rating = 400,
|
||||||
|
.read = read_hv_clock_msr,
|
||||||
|
.mask = CLOCKSOURCE_MASK(64),
|
||||||
|
.flags = CLOCK_SOURCE_IS_CONTINUOUS,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void *hypercall_pg;
|
||||||
|
struct clocksource *hyperv_cs;
|
||||||
|
EXPORT_SYMBOL_GPL(hyperv_cs);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This function is to be invoked early in the boot sequence after the
|
||||||
|
* hypervisor has been detected.
|
||||||
|
*
|
||||||
|
* 1. Setup the hypercall page.
|
||||||
|
* 2. Register Hyper-V specific clocksource.
|
||||||
|
*/
|
||||||
|
void hyperv_init(void)
|
||||||
|
{
|
||||||
|
u64 guest_id;
|
||||||
|
union hv_x64_msr_hypercall_contents hypercall_msr;
|
||||||
|
|
||||||
|
if (x86_hyper != &x86_hyper_ms_hyperv)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Setup the hypercall page and enable hypercalls.
|
||||||
|
* 1. Register the guest ID
|
||||||
|
* 2. Enable the hypercall and register the hypercall page
|
||||||
|
*/
|
||||||
|
guest_id = generate_guest_id(0, LINUX_VERSION_CODE, 0);
|
||||||
|
wrmsrl(HV_X64_MSR_GUEST_OS_ID, guest_id);
|
||||||
|
|
||||||
|
hypercall_pg = __vmalloc(PAGE_SIZE, GFP_KERNEL, PAGE_KERNEL_RX);
|
||||||
|
if (hypercall_pg == NULL) {
|
||||||
|
wrmsrl(HV_X64_MSR_GUEST_OS_ID, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
rdmsrl(HV_X64_MSR_HYPERCALL, hypercall_msr.as_uint64);
|
||||||
|
hypercall_msr.enable = 1;
|
||||||
|
hypercall_msr.guest_physical_address = vmalloc_to_pfn(hypercall_pg);
|
||||||
|
wrmsrl(HV_X64_MSR_HYPERCALL, hypercall_msr.as_uint64);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Register Hyper-V specific clocksource.
|
||||||
|
*/
|
||||||
|
#ifdef CONFIG_X86_64
|
||||||
|
if (ms_hyperv.features & HV_X64_MSR_REFERENCE_TSC_AVAILABLE) {
|
||||||
|
union hv_x64_msr_hypercall_contents tsc_msr;
|
||||||
|
|
||||||
|
tsc_pg = __vmalloc(PAGE_SIZE, GFP_KERNEL, PAGE_KERNEL);
|
||||||
|
if (!tsc_pg)
|
||||||
|
goto register_msr_cs;
|
||||||
|
|
||||||
|
hyperv_cs = &hyperv_cs_tsc;
|
||||||
|
|
||||||
|
rdmsrl(HV_X64_MSR_REFERENCE_TSC, tsc_msr.as_uint64);
|
||||||
|
|
||||||
|
tsc_msr.enable = 1;
|
||||||
|
tsc_msr.guest_physical_address = vmalloc_to_pfn(tsc_pg);
|
||||||
|
|
||||||
|
wrmsrl(HV_X64_MSR_REFERENCE_TSC, tsc_msr.as_uint64);
|
||||||
|
clocksource_register_hz(&hyperv_cs_tsc, NSEC_PER_SEC/100);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
/*
|
||||||
|
* For 32 bit guests just use the MSR based mechanism for reading
|
||||||
|
* the partition counter.
|
||||||
|
*/
|
||||||
|
|
||||||
|
register_msr_cs:
|
||||||
|
hyperv_cs = &hyperv_cs_msr;
|
||||||
|
if (ms_hyperv.features & HV_X64_MSR_TIME_REF_COUNT_AVAILABLE)
|
||||||
|
clocksource_register_hz(&hyperv_cs_msr, NSEC_PER_SEC/100);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This routine is called before kexec/kdump, it does the required cleanup.
|
||||||
|
*/
|
||||||
|
void hyperv_cleanup(void)
|
||||||
|
{
|
||||||
|
union hv_x64_msr_hypercall_contents hypercall_msr;
|
||||||
|
|
||||||
|
/* Reset our OS id */
|
||||||
|
wrmsrl(HV_X64_MSR_GUEST_OS_ID, 0);
|
||||||
|
|
||||||
|
/* Reset the hypercall page */
|
||||||
|
hypercall_msr.as_uint64 = 0;
|
||||||
|
wrmsrl(HV_X64_MSR_HYPERCALL, hypercall_msr.as_uint64);
|
||||||
|
|
||||||
|
/* Reset the TSC page */
|
||||||
|
hypercall_msr.as_uint64 = 0;
|
||||||
|
wrmsrl(HV_X64_MSR_REFERENCE_TSC, hypercall_msr.as_uint64);
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(hyperv_cleanup);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* hv_do_hypercall- Invoke the specified hypercall
|
||||||
|
*/
|
||||||
|
u64 hv_do_hypercall(u64 control, void *input, void *output)
|
||||||
|
{
|
||||||
|
u64 input_address = (input) ? virt_to_phys(input) : 0;
|
||||||
|
u64 output_address = (output) ? virt_to_phys(output) : 0;
|
||||||
|
#ifdef CONFIG_X86_64
|
||||||
|
u64 hv_status = 0;
|
||||||
|
|
||||||
|
if (!hypercall_pg)
|
||||||
|
return (u64)ULLONG_MAX;
|
||||||
|
|
||||||
|
__asm__ __volatile__("mov %0, %%r8" : : "r" (output_address) : "r8");
|
||||||
|
__asm__ __volatile__("call *%3" : "=a" (hv_status) :
|
||||||
|
"c" (control), "d" (input_address),
|
||||||
|
"m" (hypercall_pg));
|
||||||
|
|
||||||
|
return hv_status;
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
u32 control_hi = control >> 32;
|
||||||
|
u32 control_lo = control & 0xFFFFFFFF;
|
||||||
|
u32 hv_status_hi = 1;
|
||||||
|
u32 hv_status_lo = 1;
|
||||||
|
u32 input_address_hi = input_address >> 32;
|
||||||
|
u32 input_address_lo = input_address & 0xFFFFFFFF;
|
||||||
|
u32 output_address_hi = output_address >> 32;
|
||||||
|
u32 output_address_lo = output_address & 0xFFFFFFFF;
|
||||||
|
|
||||||
|
if (!hypercall_pg)
|
||||||
|
return (u64)ULLONG_MAX;
|
||||||
|
|
||||||
|
__asm__ __volatile__ ("call *%8" : "=d"(hv_status_hi),
|
||||||
|
"=a"(hv_status_lo) : "d" (control_hi),
|
||||||
|
"a" (control_lo), "b" (input_address_hi),
|
||||||
|
"c" (input_address_lo), "D"(output_address_hi),
|
||||||
|
"S"(output_address_lo), "m" (hypercall_pg));
|
||||||
|
|
||||||
|
return hv_status_lo | ((u64)hv_status_hi << 32);
|
||||||
|
#endif /* !x86_64 */
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(hv_do_hypercall);
|
||||||
|
|
||||||
|
void hyperv_report_panic(struct pt_regs *regs)
|
||||||
|
{
|
||||||
|
static bool panic_reported;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We prefer to report panic on 'die' chain as we have proper
|
||||||
|
* registers to report, but if we miss it (e.g. on BUG()) we need
|
||||||
|
* to report it on 'panic'.
|
||||||
|
*/
|
||||||
|
if (panic_reported)
|
||||||
|
return;
|
||||||
|
panic_reported = true;
|
||||||
|
|
||||||
|
wrmsrl(HV_X64_MSR_CRASH_P0, regs->ip);
|
||||||
|
wrmsrl(HV_X64_MSR_CRASH_P1, regs->ax);
|
||||||
|
wrmsrl(HV_X64_MSR_CRASH_P2, regs->bx);
|
||||||
|
wrmsrl(HV_X64_MSR_CRASH_P3, regs->cx);
|
||||||
|
wrmsrl(HV_X64_MSR_CRASH_P4, regs->dx);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Let Hyper-V know there is crash data available
|
||||||
|
*/
|
||||||
|
wrmsrl(HV_X64_MSR_CRASH_CTL, HV_CRASH_CTL_CRASH_NOTIFY);
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(hyperv_report_panic);
|
||||||
|
|
||||||
|
bool hv_is_hypercall_page_setup(void)
|
||||||
|
{
|
||||||
|
union hv_x64_msr_hypercall_contents hypercall_msr;
|
||||||
|
|
||||||
|
/* Check if the hypercall page is setup */
|
||||||
|
hypercall_msr.as_uint64 = 0;
|
||||||
|
rdmsrl(HV_X64_MSR_HYPERCALL, hypercall_msr.as_uint64);
|
||||||
|
|
||||||
|
if (!hypercall_msr.enable)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(hv_is_hypercall_page_setup);
|
|
@ -3,8 +3,28 @@
|
||||||
|
|
||||||
#include <linux/types.h>
|
#include <linux/types.h>
|
||||||
#include <linux/interrupt.h>
|
#include <linux/interrupt.h>
|
||||||
|
#include <linux/clocksource.h>
|
||||||
#include <asm/hyperv.h>
|
#include <asm/hyperv.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The below CPUID leaves are present if VersionAndFeatures.HypervisorPresent
|
||||||
|
* is set by CPUID(HVCPUID_VERSION_FEATURES).
|
||||||
|
*/
|
||||||
|
enum hv_cpuid_function {
|
||||||
|
HVCPUID_VERSION_FEATURES = 0x00000001,
|
||||||
|
HVCPUID_VENDOR_MAXFUNCTION = 0x40000000,
|
||||||
|
HVCPUID_INTERFACE = 0x40000001,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The remaining functions depend on the value of
|
||||||
|
* HVCPUID_INTERFACE
|
||||||
|
*/
|
||||||
|
HVCPUID_VERSION = 0x40000002,
|
||||||
|
HVCPUID_FEATURES = 0x40000003,
|
||||||
|
HVCPUID_ENLIGHTENMENT_INFO = 0x40000004,
|
||||||
|
HVCPUID_IMPLEMENTATION_LIMITS = 0x40000005,
|
||||||
|
};
|
||||||
|
|
||||||
struct ms_hyperv_info {
|
struct ms_hyperv_info {
|
||||||
u32 features;
|
u32 features;
|
||||||
u32 misc_features;
|
u32 misc_features;
|
||||||
|
@ -13,6 +33,128 @@ struct ms_hyperv_info {
|
||||||
|
|
||||||
extern struct ms_hyperv_info ms_hyperv;
|
extern struct ms_hyperv_info ms_hyperv;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Declare the MSR used to setup pages used to communicate with the hypervisor.
|
||||||
|
*/
|
||||||
|
union hv_x64_msr_hypercall_contents {
|
||||||
|
u64 as_uint64;
|
||||||
|
struct {
|
||||||
|
u64 enable:1;
|
||||||
|
u64 reserved:11;
|
||||||
|
u64 guest_physical_address:52;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TSC page layout.
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct ms_hyperv_tsc_page {
|
||||||
|
volatile u32 tsc_sequence;
|
||||||
|
u32 reserved1;
|
||||||
|
volatile u64 tsc_scale;
|
||||||
|
volatile s64 tsc_offset;
|
||||||
|
u64 reserved2[509];
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The guest OS needs to register the guest ID with the hypervisor.
|
||||||
|
* The guest ID is a 64 bit entity and the structure of this ID is
|
||||||
|
* specified in the Hyper-V specification:
|
||||||
|
*
|
||||||
|
* msdn.microsoft.com/en-us/library/windows/hardware/ff542653%28v=vs.85%29.aspx
|
||||||
|
*
|
||||||
|
* While the current guideline does not specify how Linux guest ID(s)
|
||||||
|
* need to be generated, our plan is to publish the guidelines for
|
||||||
|
* Linux and other guest operating systems that currently are hosted
|
||||||
|
* on Hyper-V. The implementation here conforms to this yet
|
||||||
|
* unpublished guidelines.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Bit(s)
|
||||||
|
* 63 - Indicates if the OS is Open Source or not; 1 is Open Source
|
||||||
|
* 62:56 - Os Type; Linux is 0x100
|
||||||
|
* 55:48 - Distro specific identification
|
||||||
|
* 47:16 - Linux kernel version number
|
||||||
|
* 15:0 - Distro specific identification
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define HV_LINUX_VENDOR_ID 0x8100
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Generate the guest ID based on the guideline described above.
|
||||||
|
*/
|
||||||
|
|
||||||
|
static inline __u64 generate_guest_id(__u64 d_info1, __u64 kernel_version,
|
||||||
|
__u64 d_info2)
|
||||||
|
{
|
||||||
|
__u64 guest_id = 0;
|
||||||
|
|
||||||
|
guest_id = (((__u64)HV_LINUX_VENDOR_ID) << 48);
|
||||||
|
guest_id |= (d_info1 << 48);
|
||||||
|
guest_id |= (kernel_version << 16);
|
||||||
|
guest_id |= d_info2;
|
||||||
|
|
||||||
|
return guest_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Free the message slot and signal end-of-message if required */
|
||||||
|
static inline void vmbus_signal_eom(struct hv_message *msg, u32 old_msg_type)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* On crash we're reading some other CPU's message page and we need
|
||||||
|
* to be careful: this other CPU may already had cleared the header
|
||||||
|
* and the host may already had delivered some other message there.
|
||||||
|
* In case we blindly write msg->header.message_type we're going
|
||||||
|
* to lose it. We can still lose a message of the same type but
|
||||||
|
* we count on the fact that there can only be one
|
||||||
|
* CHANNELMSG_UNLOAD_RESPONSE and we don't care about other messages
|
||||||
|
* on crash.
|
||||||
|
*/
|
||||||
|
if (cmpxchg(&msg->header.message_type, old_msg_type,
|
||||||
|
HVMSG_NONE) != old_msg_type)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Make sure the write to MessageType (ie set to
|
||||||
|
* HVMSG_NONE) happens before we read the
|
||||||
|
* MessagePending and EOMing. Otherwise, the EOMing
|
||||||
|
* will not deliver any more messages since there is
|
||||||
|
* no empty slot
|
||||||
|
*/
|
||||||
|
mb();
|
||||||
|
|
||||||
|
if (msg->header.message_flags.msg_pending) {
|
||||||
|
/*
|
||||||
|
* This will cause message queue rescan to
|
||||||
|
* possibly deliver another msg from the
|
||||||
|
* hypervisor
|
||||||
|
*/
|
||||||
|
wrmsrl(HV_X64_MSR_EOM, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#define hv_get_current_tick(tick) rdmsrl(HV_X64_MSR_TIME_REF_COUNT, tick)
|
||||||
|
#define hv_init_timer(timer, tick) wrmsrl(timer, tick)
|
||||||
|
#define hv_init_timer_config(config, val) wrmsrl(config, val)
|
||||||
|
|
||||||
|
#define hv_get_simp(val) rdmsrl(HV_X64_MSR_SIMP, val)
|
||||||
|
#define hv_set_simp(val) wrmsrl(HV_X64_MSR_SIMP, val)
|
||||||
|
|
||||||
|
#define hv_get_siefp(val) rdmsrl(HV_X64_MSR_SIEFP, val)
|
||||||
|
#define hv_set_siefp(val) wrmsrl(HV_X64_MSR_SIEFP, val)
|
||||||
|
|
||||||
|
#define hv_get_synic_state(val) rdmsrl(HV_X64_MSR_SCONTROL, val)
|
||||||
|
#define hv_set_synic_state(val) wrmsrl(HV_X64_MSR_SCONTROL, val)
|
||||||
|
|
||||||
|
#define hv_get_vp_index(index) rdmsrl(HV_X64_MSR_VP_INDEX, index)
|
||||||
|
|
||||||
|
#define hv_get_synint_state(int_num, val) rdmsrl(int_num, val)
|
||||||
|
#define hv_set_synint_state(int_num, val) wrmsrl(int_num, val)
|
||||||
|
|
||||||
void hyperv_callback_vector(void);
|
void hyperv_callback_vector(void);
|
||||||
#ifdef CONFIG_TRACING
|
#ifdef CONFIG_TRACING
|
||||||
#define trace_hyperv_callback_vector hyperv_callback_vector
|
#define trace_hyperv_callback_vector hyperv_callback_vector
|
||||||
|
@ -25,4 +167,13 @@ void hv_setup_kexec_handler(void (*handler)(void));
|
||||||
void hv_remove_kexec_handler(void);
|
void hv_remove_kexec_handler(void);
|
||||||
void hv_setup_crash_handler(void (*handler)(struct pt_regs *regs));
|
void hv_setup_crash_handler(void (*handler)(struct pt_regs *regs));
|
||||||
void hv_remove_crash_handler(void);
|
void hv_remove_crash_handler(void);
|
||||||
|
|
||||||
|
#if IS_ENABLED(CONFIG_HYPERV)
|
||||||
|
extern struct clocksource *hyperv_cs;
|
||||||
|
|
||||||
|
void hyperv_init(void);
|
||||||
|
void hyperv_report_panic(struct pt_regs *regs);
|
||||||
|
bool hv_is_hypercall_page_setup(void);
|
||||||
|
void hyperv_cleanup(void);
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -73,6 +73,9 @@
|
||||||
*/
|
*/
|
||||||
#define HV_X64_MSR_STAT_PAGES_AVAILABLE (1 << 8)
|
#define HV_X64_MSR_STAT_PAGES_AVAILABLE (1 << 8)
|
||||||
|
|
||||||
|
/* Crash MSR available */
|
||||||
|
#define HV_FEATURE_GUEST_CRASH_MSR_AVAILABLE (1 << 10)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Feature identification: EBX indicates which flags were specified at
|
* Feature identification: EBX indicates which flags were specified at
|
||||||
* partition creation. The format is the same as the partition creation
|
* partition creation. The format is the same as the partition creation
|
||||||
|
@ -144,6 +147,11 @@
|
||||||
*/
|
*/
|
||||||
#define HV_X64_RELAXED_TIMING_RECOMMENDED (1 << 5)
|
#define HV_X64_RELAXED_TIMING_RECOMMENDED (1 << 5)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Crash notification flag.
|
||||||
|
*/
|
||||||
|
#define HV_CRASH_CTL_CRASH_NOTIFY (1ULL << 63)
|
||||||
|
|
||||||
/* MSR used to identify the guest OS. */
|
/* MSR used to identify the guest OS. */
|
||||||
#define HV_X64_MSR_GUEST_OS_ID 0x40000000
|
#define HV_X64_MSR_GUEST_OS_ID 0x40000000
|
||||||
|
|
||||||
|
|
|
@ -133,26 +133,6 @@ static uint32_t __init ms_hyperv_platform(void)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static u64 read_hv_clock(struct clocksource *arg)
|
|
||||||
{
|
|
||||||
u64 current_tick;
|
|
||||||
/*
|
|
||||||
* Read the partition counter to get the current tick count. This count
|
|
||||||
* is set to 0 when the partition is created and is incremented in
|
|
||||||
* 100 nanosecond units.
|
|
||||||
*/
|
|
||||||
rdmsrl(HV_X64_MSR_TIME_REF_COUNT, current_tick);
|
|
||||||
return current_tick;
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct clocksource hyperv_cs = {
|
|
||||||
.name = "hyperv_clocksource",
|
|
||||||
.rating = 400, /* use this when running on Hyperv*/
|
|
||||||
.read = read_hv_clock,
|
|
||||||
.mask = CLOCKSOURCE_MASK(64),
|
|
||||||
.flags = CLOCK_SOURCE_IS_CONTINUOUS,
|
|
||||||
};
|
|
||||||
|
|
||||||
static unsigned char hv_get_nmi_reason(void)
|
static unsigned char hv_get_nmi_reason(void)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -180,6 +160,11 @@ static int hv_nmi_unknown(unsigned int val, struct pt_regs *regs)
|
||||||
|
|
||||||
static void __init ms_hyperv_init_platform(void)
|
static void __init ms_hyperv_init_platform(void)
|
||||||
{
|
{
|
||||||
|
int hv_host_info_eax;
|
||||||
|
int hv_host_info_ebx;
|
||||||
|
int hv_host_info_ecx;
|
||||||
|
int hv_host_info_edx;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Extract the features and hints
|
* Extract the features and hints
|
||||||
*/
|
*/
|
||||||
|
@ -190,6 +175,21 @@ static void __init ms_hyperv_init_platform(void)
|
||||||
pr_info("HyperV: features 0x%x, hints 0x%x\n",
|
pr_info("HyperV: features 0x%x, hints 0x%x\n",
|
||||||
ms_hyperv.features, ms_hyperv.hints);
|
ms_hyperv.features, ms_hyperv.hints);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Extract host information.
|
||||||
|
*/
|
||||||
|
if (cpuid_eax(HVCPUID_VENDOR_MAXFUNCTION) >= HVCPUID_VERSION) {
|
||||||
|
hv_host_info_eax = cpuid_eax(HVCPUID_VERSION);
|
||||||
|
hv_host_info_ebx = cpuid_ebx(HVCPUID_VERSION);
|
||||||
|
hv_host_info_ecx = cpuid_ecx(HVCPUID_VERSION);
|
||||||
|
hv_host_info_edx = cpuid_edx(HVCPUID_VERSION);
|
||||||
|
|
||||||
|
pr_info("Hyper-V Host Build:%d-%d.%d-%d-%d.%d\n",
|
||||||
|
hv_host_info_eax, hv_host_info_ebx >> 16,
|
||||||
|
hv_host_info_ebx & 0xFFFF, hv_host_info_ecx,
|
||||||
|
hv_host_info_edx >> 24, hv_host_info_edx & 0xFFFFFF);
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef CONFIG_X86_LOCAL_APIC
|
#ifdef CONFIG_X86_LOCAL_APIC
|
||||||
if (ms_hyperv.features & HV_X64_MSR_APIC_FREQUENCY_AVAILABLE) {
|
if (ms_hyperv.features & HV_X64_MSR_APIC_FREQUENCY_AVAILABLE) {
|
||||||
/*
|
/*
|
||||||
|
@ -208,9 +208,6 @@ static void __init ms_hyperv_init_platform(void)
|
||||||
"hv_nmi_unknown");
|
"hv_nmi_unknown");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (ms_hyperv.features & HV_X64_MSR_TIME_REF_COUNT_AVAILABLE)
|
|
||||||
clocksource_register_hz(&hyperv_cs, NSEC_PER_SEC/100);
|
|
||||||
|
|
||||||
#ifdef CONFIG_X86_IO_APIC
|
#ifdef CONFIG_X86_IO_APIC
|
||||||
no_timer_check = 1;
|
no_timer_check = 1;
|
||||||
#endif
|
#endif
|
||||||
|
@ -227,6 +224,13 @@ static void __init ms_hyperv_init_platform(void)
|
||||||
*/
|
*/
|
||||||
if (efi_enabled(EFI_BOOT))
|
if (efi_enabled(EFI_BOOT))
|
||||||
x86_platform.get_nmi_reason = hv_get_nmi_reason;
|
x86_platform.get_nmi_reason = hv_get_nmi_reason;
|
||||||
|
|
||||||
|
#if IS_ENABLED(CONFIG_HYPERV)
|
||||||
|
/*
|
||||||
|
* Setup the hook to get control post apic initialization.
|
||||||
|
*/
|
||||||
|
x86_platform.apic_post_init = hyperv_init;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
const __refconst struct hypervisor_x86 x86_hyper_ms_hyperv = {
|
const __refconst struct hypervisor_x86 x86_hyper_ms_hyperv = {
|
||||||
|
|
|
@ -42,8 +42,20 @@ static struct resource goldfish_pdev_bus_resources[] = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static bool goldfish_enable __initdata;
|
||||||
|
|
||||||
|
static int __init goldfish_setup(char *str)
|
||||||
|
{
|
||||||
|
goldfish_enable = true;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
__setup("goldfish", goldfish_setup);
|
||||||
|
|
||||||
static int __init goldfish_init(void)
|
static int __init goldfish_init(void)
|
||||||
{
|
{
|
||||||
|
if (!goldfish_enable)
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
platform_device_register_simple("goldfish_pdev_bus", -1,
|
platform_device_register_simple("goldfish_pdev_bus", -1,
|
||||||
goldfish_pdev_bus_resources, 2);
|
goldfish_pdev_bus_resources, 2);
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -202,4 +202,6 @@ source "drivers/hwtracing/intel_th/Kconfig"
|
||||||
|
|
||||||
source "drivers/fpga/Kconfig"
|
source "drivers/fpga/Kconfig"
|
||||||
|
|
||||||
|
source "drivers/fsi/Kconfig"
|
||||||
|
|
||||||
endmenu
|
endmenu
|
||||||
|
|
|
@ -173,3 +173,4 @@ obj-$(CONFIG_STM) += hwtracing/stm/
|
||||||
obj-$(CONFIG_ANDROID) += android/
|
obj-$(CONFIG_ANDROID) += android/
|
||||||
obj-$(CONFIG_NVMEM) += nvmem/
|
obj-$(CONFIG_NVMEM) += nvmem/
|
||||||
obj-$(CONFIG_FPGA) += fpga/
|
obj-$(CONFIG_FPGA) += fpga/
|
||||||
|
obj-$(CONFIG_FSI) += fsi/
|
||||||
|
|
|
@ -19,6 +19,18 @@ config ANDROID_BINDER_IPC
|
||||||
Android process, using Binder to identify, invoke and pass arguments
|
Android process, using Binder to identify, invoke and pass arguments
|
||||||
between said processes.
|
between said processes.
|
||||||
|
|
||||||
|
config ANDROID_BINDER_DEVICES
|
||||||
|
string "Android Binder devices"
|
||||||
|
depends on ANDROID_BINDER_IPC
|
||||||
|
default "binder"
|
||||||
|
---help---
|
||||||
|
Default value for the binder.devices parameter.
|
||||||
|
|
||||||
|
The binder.devices parameter is a comma-separated list of strings
|
||||||
|
that specifies the names of the binder device nodes that will be
|
||||||
|
created. Each binder device has its own context manager, and is
|
||||||
|
therefore logically separated from the other devices.
|
||||||
|
|
||||||
config ANDROID_BINDER_IPC_32BIT
|
config ANDROID_BINDER_IPC_32BIT
|
||||||
bool
|
bool
|
||||||
depends on !64BIT && ANDROID_BINDER_IPC
|
depends on !64BIT && ANDROID_BINDER_IPC
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -56,14 +56,16 @@
|
||||||
#define HT16K33_FB_SIZE (HT16K33_MATRIX_LED_MAX_COLS * BYTES_PER_ROW)
|
#define HT16K33_FB_SIZE (HT16K33_MATRIX_LED_MAX_COLS * BYTES_PER_ROW)
|
||||||
|
|
||||||
struct ht16k33_keypad {
|
struct ht16k33_keypad {
|
||||||
|
struct i2c_client *client;
|
||||||
struct input_dev *dev;
|
struct input_dev *dev;
|
||||||
spinlock_t lock;
|
|
||||||
struct delayed_work work;
|
|
||||||
uint32_t cols;
|
uint32_t cols;
|
||||||
uint32_t rows;
|
uint32_t rows;
|
||||||
uint32_t row_shift;
|
uint32_t row_shift;
|
||||||
uint32_t debounce_ms;
|
uint32_t debounce_ms;
|
||||||
uint16_t last_key_state[HT16K33_MATRIX_KEYPAD_MAX_COLS];
|
uint16_t last_key_state[HT16K33_MATRIX_KEYPAD_MAX_COLS];
|
||||||
|
|
||||||
|
wait_queue_head_t wait;
|
||||||
|
bool stopped;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ht16k33_fbdev {
|
struct ht16k33_fbdev {
|
||||||
|
@ -78,7 +80,6 @@ struct ht16k33_priv {
|
||||||
struct i2c_client *client;
|
struct i2c_client *client;
|
||||||
struct ht16k33_keypad keypad;
|
struct ht16k33_keypad keypad;
|
||||||
struct ht16k33_fbdev fbdev;
|
struct ht16k33_fbdev fbdev;
|
||||||
struct workqueue_struct *workqueue;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static struct fb_fix_screeninfo ht16k33_fb_fix = {
|
static struct fb_fix_screeninfo ht16k33_fb_fix = {
|
||||||
|
@ -124,18 +125,10 @@ static void ht16k33_fb_queue(struct ht16k33_priv *priv)
|
||||||
{
|
{
|
||||||
struct ht16k33_fbdev *fbdev = &priv->fbdev;
|
struct ht16k33_fbdev *fbdev = &priv->fbdev;
|
||||||
|
|
||||||
queue_delayed_work(priv->workqueue, &fbdev->work,
|
schedule_delayed_work(&fbdev->work,
|
||||||
msecs_to_jiffies(HZ / fbdev->refresh_rate));
|
msecs_to_jiffies(HZ / fbdev->refresh_rate));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ht16k33_keypad_queue(struct ht16k33_priv *priv)
|
|
||||||
{
|
|
||||||
struct ht16k33_keypad *keypad = &priv->keypad;
|
|
||||||
|
|
||||||
queue_delayed_work(priv->workqueue, &keypad->work,
|
|
||||||
msecs_to_jiffies(keypad->debounce_ms));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This gets the fb data from cache and copies it to ht16k33 display RAM
|
* This gets the fb data from cache and copies it to ht16k33 display RAM
|
||||||
*/
|
*/
|
||||||
|
@ -182,32 +175,6 @@ static void ht16k33_fb_update(struct work_struct *work)
|
||||||
ht16k33_fb_queue(priv);
|
ht16k33_fb_queue(priv);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int ht16k33_keypad_start(struct input_dev *dev)
|
|
||||||
{
|
|
||||||
struct ht16k33_priv *priv = input_get_drvdata(dev);
|
|
||||||
struct ht16k33_keypad *keypad = &priv->keypad;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Schedule an immediate key scan to capture current key state;
|
|
||||||
* columns will be activated and IRQs be enabled after the scan.
|
|
||||||
*/
|
|
||||||
queue_delayed_work(priv->workqueue, &keypad->work, 0);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void ht16k33_keypad_stop(struct input_dev *dev)
|
|
||||||
{
|
|
||||||
struct ht16k33_priv *priv = input_get_drvdata(dev);
|
|
||||||
struct ht16k33_keypad *keypad = &priv->keypad;
|
|
||||||
|
|
||||||
cancel_delayed_work(&keypad->work);
|
|
||||||
/*
|
|
||||||
* ht16k33_keypad_scan() will leave IRQs enabled;
|
|
||||||
* we should disable them now.
|
|
||||||
*/
|
|
||||||
disable_irq_nosync(priv->client->irq);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int ht16k33_initialize(struct ht16k33_priv *priv)
|
static int ht16k33_initialize(struct ht16k33_priv *priv)
|
||||||
{
|
{
|
||||||
uint8_t byte;
|
uint8_t byte;
|
||||||
|
@ -233,61 +200,6 @@ static int ht16k33_initialize(struct ht16k33_priv *priv)
|
||||||
return i2c_smbus_write_byte(priv->client, byte);
|
return i2c_smbus_write_byte(priv->client, byte);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* This gets the keys from keypad and reports it to input subsystem
|
|
||||||
*/
|
|
||||||
static void ht16k33_keypad_scan(struct work_struct *work)
|
|
||||||
{
|
|
||||||
struct ht16k33_keypad *keypad =
|
|
||||||
container_of(work, struct ht16k33_keypad, work.work);
|
|
||||||
struct ht16k33_priv *priv =
|
|
||||||
container_of(keypad, struct ht16k33_priv, keypad);
|
|
||||||
const unsigned short *keycodes = keypad->dev->keycode;
|
|
||||||
uint16_t bits_changed, new_state[HT16K33_MATRIX_KEYPAD_MAX_COLS];
|
|
||||||
uint8_t data[HT16K33_MATRIX_KEYPAD_MAX_COLS * 2];
|
|
||||||
int row, col, code;
|
|
||||||
bool reschedule = false;
|
|
||||||
|
|
||||||
if (i2c_smbus_read_i2c_block_data(priv->client, 0x40, 6, data) != 6) {
|
|
||||||
dev_err(&priv->client->dev, "Failed to read key data\n");
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (col = 0; col < keypad->cols; col++) {
|
|
||||||
new_state[col] = (data[col * 2 + 1] << 8) | data[col * 2];
|
|
||||||
if (new_state[col])
|
|
||||||
reschedule = true;
|
|
||||||
bits_changed = keypad->last_key_state[col] ^ new_state[col];
|
|
||||||
|
|
||||||
while (bits_changed) {
|
|
||||||
row = ffs(bits_changed) - 1;
|
|
||||||
code = MATRIX_SCAN_CODE(row, col, keypad->row_shift);
|
|
||||||
input_event(keypad->dev, EV_MSC, MSC_SCAN, code);
|
|
||||||
input_report_key(keypad->dev, keycodes[code],
|
|
||||||
new_state[col] & BIT(row));
|
|
||||||
bits_changed &= ~BIT(row);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
input_sync(keypad->dev);
|
|
||||||
memcpy(keypad->last_key_state, new_state, sizeof(new_state));
|
|
||||||
|
|
||||||
end:
|
|
||||||
if (reschedule)
|
|
||||||
ht16k33_keypad_queue(priv);
|
|
||||||
else
|
|
||||||
enable_irq(priv->client->irq);
|
|
||||||
}
|
|
||||||
|
|
||||||
static irqreturn_t ht16k33_irq_thread(int irq, void *dev)
|
|
||||||
{
|
|
||||||
struct ht16k33_priv *priv = dev;
|
|
||||||
|
|
||||||
disable_irq_nosync(priv->client->irq);
|
|
||||||
ht16k33_keypad_queue(priv);
|
|
||||||
|
|
||||||
return IRQ_HANDLED;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int ht16k33_bl_update_status(struct backlight_device *bl)
|
static int ht16k33_bl_update_status(struct backlight_device *bl)
|
||||||
{
|
{
|
||||||
int brightness = bl->props.brightness;
|
int brightness = bl->props.brightness;
|
||||||
|
@ -334,15 +246,152 @@ static struct fb_ops ht16k33_fb_ops = {
|
||||||
.fb_mmap = ht16k33_mmap,
|
.fb_mmap = ht16k33_mmap,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This gets the keys from keypad and reports it to input subsystem.
|
||||||
|
* Returns true if a key is pressed.
|
||||||
|
*/
|
||||||
|
static bool ht16k33_keypad_scan(struct ht16k33_keypad *keypad)
|
||||||
|
{
|
||||||
|
const unsigned short *keycodes = keypad->dev->keycode;
|
||||||
|
u16 new_state[HT16K33_MATRIX_KEYPAD_MAX_COLS];
|
||||||
|
u8 data[HT16K33_MATRIX_KEYPAD_MAX_COLS * 2];
|
||||||
|
unsigned long bits_changed;
|
||||||
|
int row, col, code;
|
||||||
|
bool pressed = false;
|
||||||
|
|
||||||
|
if (i2c_smbus_read_i2c_block_data(keypad->client, 0x40, 6, data) != 6) {
|
||||||
|
dev_err(&keypad->client->dev, "Failed to read key data\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (col = 0; col < keypad->cols; col++) {
|
||||||
|
new_state[col] = (data[col * 2 + 1] << 8) | data[col * 2];
|
||||||
|
if (new_state[col])
|
||||||
|
pressed = true;
|
||||||
|
bits_changed = keypad->last_key_state[col] ^ new_state[col];
|
||||||
|
|
||||||
|
for_each_set_bit(row, &bits_changed, BITS_PER_LONG) {
|
||||||
|
code = MATRIX_SCAN_CODE(row, col, keypad->row_shift);
|
||||||
|
input_event(keypad->dev, EV_MSC, MSC_SCAN, code);
|
||||||
|
input_report_key(keypad->dev, keycodes[code],
|
||||||
|
new_state[col] & BIT(row));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
input_sync(keypad->dev);
|
||||||
|
memcpy(keypad->last_key_state, new_state, sizeof(new_state));
|
||||||
|
|
||||||
|
return pressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
static irqreturn_t ht16k33_keypad_irq_thread(int irq, void *dev)
|
||||||
|
{
|
||||||
|
struct ht16k33_keypad *keypad = dev;
|
||||||
|
|
||||||
|
do {
|
||||||
|
wait_event_timeout(keypad->wait, keypad->stopped,
|
||||||
|
msecs_to_jiffies(keypad->debounce_ms));
|
||||||
|
if (keypad->stopped)
|
||||||
|
break;
|
||||||
|
} while (ht16k33_keypad_scan(keypad));
|
||||||
|
|
||||||
|
return IRQ_HANDLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ht16k33_keypad_start(struct input_dev *dev)
|
||||||
|
{
|
||||||
|
struct ht16k33_keypad *keypad = input_get_drvdata(dev);
|
||||||
|
|
||||||
|
keypad->stopped = false;
|
||||||
|
mb();
|
||||||
|
enable_irq(keypad->client->irq);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ht16k33_keypad_stop(struct input_dev *dev)
|
||||||
|
{
|
||||||
|
struct ht16k33_keypad *keypad = input_get_drvdata(dev);
|
||||||
|
|
||||||
|
keypad->stopped = true;
|
||||||
|
mb();
|
||||||
|
wake_up(&keypad->wait);
|
||||||
|
disable_irq(keypad->client->irq);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ht16k33_keypad_probe(struct i2c_client *client,
|
||||||
|
struct ht16k33_keypad *keypad)
|
||||||
|
{
|
||||||
|
struct device_node *node = client->dev.of_node;
|
||||||
|
u32 rows = HT16K33_MATRIX_KEYPAD_MAX_ROWS;
|
||||||
|
u32 cols = HT16K33_MATRIX_KEYPAD_MAX_COLS;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
keypad->client = client;
|
||||||
|
init_waitqueue_head(&keypad->wait);
|
||||||
|
|
||||||
|
keypad->dev = devm_input_allocate_device(&client->dev);
|
||||||
|
if (!keypad->dev)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
input_set_drvdata(keypad->dev, keypad);
|
||||||
|
|
||||||
|
keypad->dev->name = DRIVER_NAME"-keypad";
|
||||||
|
keypad->dev->id.bustype = BUS_I2C;
|
||||||
|
keypad->dev->open = ht16k33_keypad_start;
|
||||||
|
keypad->dev->close = ht16k33_keypad_stop;
|
||||||
|
|
||||||
|
if (!of_get_property(node, "linux,no-autorepeat", NULL))
|
||||||
|
__set_bit(EV_REP, keypad->dev->evbit);
|
||||||
|
|
||||||
|
err = of_property_read_u32(node, "debounce-delay-ms",
|
||||||
|
&keypad->debounce_ms);
|
||||||
|
if (err) {
|
||||||
|
dev_err(&client->dev, "key debounce delay not specified\n");
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = matrix_keypad_parse_of_params(&client->dev, &rows, &cols);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
keypad->rows = rows;
|
||||||
|
keypad->cols = cols;
|
||||||
|
keypad->row_shift = get_count_order(cols);
|
||||||
|
|
||||||
|
err = matrix_keypad_build_keymap(NULL, NULL, rows, cols, NULL,
|
||||||
|
keypad->dev);
|
||||||
|
if (err) {
|
||||||
|
dev_err(&client->dev, "failed to build keymap\n");
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = devm_request_threaded_irq(&client->dev, client->irq,
|
||||||
|
NULL, ht16k33_keypad_irq_thread,
|
||||||
|
IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
|
||||||
|
DRIVER_NAME, keypad);
|
||||||
|
if (err) {
|
||||||
|
dev_err(&client->dev, "irq request failed %d, error %d\n",
|
||||||
|
client->irq, err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
ht16k33_keypad_stop(keypad->dev);
|
||||||
|
|
||||||
|
err = input_register_device(keypad->dev);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int ht16k33_probe(struct i2c_client *client,
|
static int ht16k33_probe(struct i2c_client *client,
|
||||||
const struct i2c_device_id *id)
|
const struct i2c_device_id *id)
|
||||||
{
|
{
|
||||||
int err;
|
int err;
|
||||||
uint32_t rows, cols, dft_brightness;
|
uint32_t dft_brightness;
|
||||||
struct backlight_device *bl;
|
struct backlight_device *bl;
|
||||||
struct backlight_properties bl_props;
|
struct backlight_properties bl_props;
|
||||||
struct ht16k33_priv *priv;
|
struct ht16k33_priv *priv;
|
||||||
struct ht16k33_keypad *keypad;
|
|
||||||
struct ht16k33_fbdev *fbdev;
|
struct ht16k33_fbdev *fbdev;
|
||||||
struct device_node *node = client->dev.of_node;
|
struct device_node *node = client->dev.of_node;
|
||||||
|
|
||||||
|
@ -363,23 +412,16 @@ static int ht16k33_probe(struct i2c_client *client,
|
||||||
priv->client = client;
|
priv->client = client;
|
||||||
i2c_set_clientdata(client, priv);
|
i2c_set_clientdata(client, priv);
|
||||||
fbdev = &priv->fbdev;
|
fbdev = &priv->fbdev;
|
||||||
keypad = &priv->keypad;
|
|
||||||
|
|
||||||
priv->workqueue = create_singlethread_workqueue(DRIVER_NAME "-wq");
|
|
||||||
if (priv->workqueue == NULL)
|
|
||||||
return -ENOMEM;
|
|
||||||
|
|
||||||
err = ht16k33_initialize(priv);
|
err = ht16k33_initialize(priv);
|
||||||
if (err)
|
if (err)
|
||||||
goto err_destroy_wq;
|
return err;
|
||||||
|
|
||||||
/* Framebuffer (2 bytes per column) */
|
/* Framebuffer (2 bytes per column) */
|
||||||
BUILD_BUG_ON(PAGE_SIZE < HT16K33_FB_SIZE);
|
BUILD_BUG_ON(PAGE_SIZE < HT16K33_FB_SIZE);
|
||||||
fbdev->buffer = (unsigned char *) get_zeroed_page(GFP_KERNEL);
|
fbdev->buffer = (unsigned char *) get_zeroed_page(GFP_KERNEL);
|
||||||
if (!fbdev->buffer) {
|
if (!fbdev->buffer)
|
||||||
err = -ENOMEM;
|
return -ENOMEM;
|
||||||
goto err_free_fbdev;
|
|
||||||
}
|
|
||||||
|
|
||||||
fbdev->cache = devm_kmalloc(&client->dev, HT16K33_FB_SIZE, GFP_KERNEL);
|
fbdev->cache = devm_kmalloc(&client->dev, HT16K33_FB_SIZE, GFP_KERNEL);
|
||||||
if (!fbdev->cache) {
|
if (!fbdev->cache) {
|
||||||
|
@ -415,59 +457,7 @@ static int ht16k33_probe(struct i2c_client *client,
|
||||||
if (err)
|
if (err)
|
||||||
goto err_fbdev_info;
|
goto err_fbdev_info;
|
||||||
|
|
||||||
/* Keypad */
|
err = ht16k33_keypad_probe(client, &priv->keypad);
|
||||||
keypad->dev = devm_input_allocate_device(&client->dev);
|
|
||||||
if (!keypad->dev) {
|
|
||||||
err = -ENOMEM;
|
|
||||||
goto err_fbdev_unregister;
|
|
||||||
}
|
|
||||||
|
|
||||||
keypad->dev->name = DRIVER_NAME"-keypad";
|
|
||||||
keypad->dev->id.bustype = BUS_I2C;
|
|
||||||
keypad->dev->open = ht16k33_keypad_start;
|
|
||||||
keypad->dev->close = ht16k33_keypad_stop;
|
|
||||||
|
|
||||||
if (!of_get_property(node, "linux,no-autorepeat", NULL))
|
|
||||||
__set_bit(EV_REP, keypad->dev->evbit);
|
|
||||||
|
|
||||||
err = of_property_read_u32(node, "debounce-delay-ms",
|
|
||||||
&keypad->debounce_ms);
|
|
||||||
if (err) {
|
|
||||||
dev_err(&client->dev, "key debounce delay not specified\n");
|
|
||||||
goto err_fbdev_unregister;
|
|
||||||
}
|
|
||||||
|
|
||||||
err = devm_request_threaded_irq(&client->dev, client->irq, NULL,
|
|
||||||
ht16k33_irq_thread,
|
|
||||||
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
|
|
||||||
DRIVER_NAME, priv);
|
|
||||||
if (err) {
|
|
||||||
dev_err(&client->dev, "irq request failed %d, error %d\n",
|
|
||||||
client->irq, err);
|
|
||||||
goto err_fbdev_unregister;
|
|
||||||
}
|
|
||||||
|
|
||||||
disable_irq_nosync(client->irq);
|
|
||||||
rows = HT16K33_MATRIX_KEYPAD_MAX_ROWS;
|
|
||||||
cols = HT16K33_MATRIX_KEYPAD_MAX_COLS;
|
|
||||||
err = matrix_keypad_parse_of_params(&client->dev, &rows, &cols);
|
|
||||||
if (err)
|
|
||||||
goto err_fbdev_unregister;
|
|
||||||
|
|
||||||
err = matrix_keypad_build_keymap(NULL, NULL, rows, cols, NULL,
|
|
||||||
keypad->dev);
|
|
||||||
if (err) {
|
|
||||||
dev_err(&client->dev, "failed to build keymap\n");
|
|
||||||
goto err_fbdev_unregister;
|
|
||||||
}
|
|
||||||
|
|
||||||
input_set_drvdata(keypad->dev, priv);
|
|
||||||
keypad->rows = rows;
|
|
||||||
keypad->cols = cols;
|
|
||||||
keypad->row_shift = get_count_order(cols);
|
|
||||||
INIT_DELAYED_WORK(&keypad->work, ht16k33_keypad_scan);
|
|
||||||
|
|
||||||
err = input_register_device(keypad->dev);
|
|
||||||
if (err)
|
if (err)
|
||||||
goto err_fbdev_unregister;
|
goto err_fbdev_unregister;
|
||||||
|
|
||||||
|
@ -482,7 +472,7 @@ static int ht16k33_probe(struct i2c_client *client,
|
||||||
if (IS_ERR(bl)) {
|
if (IS_ERR(bl)) {
|
||||||
dev_err(&client->dev, "failed to register backlight\n");
|
dev_err(&client->dev, "failed to register backlight\n");
|
||||||
err = PTR_ERR(bl);
|
err = PTR_ERR(bl);
|
||||||
goto err_keypad_unregister;
|
goto err_fbdev_unregister;
|
||||||
}
|
}
|
||||||
|
|
||||||
err = of_property_read_u32(node, "default-brightness-level",
|
err = of_property_read_u32(node, "default-brightness-level",
|
||||||
|
@ -502,18 +492,12 @@ static int ht16k33_probe(struct i2c_client *client,
|
||||||
ht16k33_fb_queue(priv);
|
ht16k33_fb_queue(priv);
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
err_keypad_unregister:
|
|
||||||
input_unregister_device(keypad->dev);
|
|
||||||
err_fbdev_unregister:
|
err_fbdev_unregister:
|
||||||
unregister_framebuffer(fbdev->info);
|
unregister_framebuffer(fbdev->info);
|
||||||
err_fbdev_info:
|
err_fbdev_info:
|
||||||
framebuffer_release(fbdev->info);
|
framebuffer_release(fbdev->info);
|
||||||
err_fbdev_buffer:
|
err_fbdev_buffer:
|
||||||
free_page((unsigned long) fbdev->buffer);
|
free_page((unsigned long) fbdev->buffer);
|
||||||
err_free_fbdev:
|
|
||||||
kfree(fbdev);
|
|
||||||
err_destroy_wq:
|
|
||||||
destroy_workqueue(priv->workqueue);
|
|
||||||
|
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
@ -521,17 +505,13 @@ static int ht16k33_probe(struct i2c_client *client,
|
||||||
static int ht16k33_remove(struct i2c_client *client)
|
static int ht16k33_remove(struct i2c_client *client)
|
||||||
{
|
{
|
||||||
struct ht16k33_priv *priv = i2c_get_clientdata(client);
|
struct ht16k33_priv *priv = i2c_get_clientdata(client);
|
||||||
struct ht16k33_keypad *keypad = &priv->keypad;
|
|
||||||
struct ht16k33_fbdev *fbdev = &priv->fbdev;
|
struct ht16k33_fbdev *fbdev = &priv->fbdev;
|
||||||
|
|
||||||
ht16k33_keypad_stop(keypad->dev);
|
|
||||||
|
|
||||||
cancel_delayed_work(&fbdev->work);
|
cancel_delayed_work(&fbdev->work);
|
||||||
unregister_framebuffer(fbdev->info);
|
unregister_framebuffer(fbdev->info);
|
||||||
framebuffer_release(fbdev->info);
|
framebuffer_release(fbdev->info);
|
||||||
free_page((unsigned long) fbdev->buffer);
|
free_page((unsigned long) fbdev->buffer);
|
||||||
|
|
||||||
destroy_workqueue(priv->workqueue);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -571,9 +571,12 @@ config TELCLOCK
|
||||||
controlling the behavior of this hardware.
|
controlling the behavior of this hardware.
|
||||||
|
|
||||||
config DEVPORT
|
config DEVPORT
|
||||||
bool
|
bool "/dev/port character device"
|
||||||
depends on ISA || PCI
|
depends on ISA || PCI
|
||||||
default y
|
default y
|
||||||
|
help
|
||||||
|
Say Y here if you want to support the /dev/port device. The /dev/port
|
||||||
|
device is similar to /dev/mem, but for I/O ports.
|
||||||
|
|
||||||
source "drivers/s390/char/Kconfig"
|
source "drivers/s390/char/Kconfig"
|
||||||
|
|
||||||
|
|
|
@ -31,13 +31,6 @@
|
||||||
#include <linux/kthread.h>
|
#include <linux/kthread.h>
|
||||||
#include <linux/delay.h>
|
#include <linux/delay.h>
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The apm_bios device is one of the misc char devices.
|
|
||||||
* This is its minor number.
|
|
||||||
*/
|
|
||||||
#define APM_MINOR_DEV 134
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* One option can be changed at boot time as follows:
|
* One option can be changed at boot time as follows:
|
||||||
* apm=on/off enable/disable APM
|
* apm=on/off enable/disable APM
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
#include <linux/init.h>
|
#include <linux/init.h>
|
||||||
#include <linux/mm.h>
|
#include <linux/mm.h>
|
||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
#include <linux/miscdevice.h>
|
|
||||||
#include <linux/delay.h>
|
#include <linux/delay.h>
|
||||||
#include <linux/bcd.h>
|
#include <linux/bcd.h>
|
||||||
#include <linux/mutex.h>
|
#include <linux/mutex.h>
|
||||||
|
|
|
@ -463,9 +463,9 @@ static int mmtimer_mmap(struct file *file, struct vm_area_struct *vma)
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct miscdevice mmtimer_miscdev = {
|
static struct miscdevice mmtimer_miscdev = {
|
||||||
SGI_MMTIMER,
|
.minor = SGI_MMTIMER,
|
||||||
MMTIMER_NAME,
|
.name = MMTIMER_NAME,
|
||||||
&mmtimer_fops
|
.fops = &mmtimer_fops
|
||||||
};
|
};
|
||||||
|
|
||||||
static struct timespec sgi_clock_offset;
|
static struct timespec sgi_clock_offset;
|
||||||
|
|
|
@ -269,7 +269,6 @@ int buffer_icap_set_configuration(struct hwicap_drvdata *drvdata, u32 *data,
|
||||||
{
|
{
|
||||||
int status;
|
int status;
|
||||||
s32 buffer_count = 0;
|
s32 buffer_count = 0;
|
||||||
s32 num_writes = 0;
|
|
||||||
bool dirty = false;
|
bool dirty = false;
|
||||||
u32 i;
|
u32 i;
|
||||||
void __iomem *base_address = drvdata->base_address;
|
void __iomem *base_address = drvdata->base_address;
|
||||||
|
@ -298,7 +297,6 @@ int buffer_icap_set_configuration(struct hwicap_drvdata *drvdata, u32 *data,
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer_count = 0;
|
buffer_count = 0;
|
||||||
num_writes++;
|
|
||||||
dirty = false;
|
dirty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -328,7 +326,6 @@ int buffer_icap_get_configuration(struct hwicap_drvdata *drvdata, u32 *data,
|
||||||
{
|
{
|
||||||
int status;
|
int status;
|
||||||
s32 buffer_count = 0;
|
s32 buffer_count = 0;
|
||||||
s32 read_count = 0;
|
|
||||||
u32 i;
|
u32 i;
|
||||||
void __iomem *base_address = drvdata->base_address;
|
void __iomem *base_address = drvdata->base_address;
|
||||||
|
|
||||||
|
@ -353,7 +350,6 @@ int buffer_icap_get_configuration(struct hwicap_drvdata *drvdata, u32 *data,
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer_count = 0;
|
buffer_count = 0;
|
||||||
read_count++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Copy data from bram */
|
/* Copy data from bram */
|
||||||
|
|
|
@ -42,6 +42,16 @@ config EXTCON_GPIO
|
||||||
Say Y here to enable GPIO based extcon support. Note that GPIO
|
Say Y here to enable GPIO based extcon support. Note that GPIO
|
||||||
extcon supports single state per extcon instance.
|
extcon supports single state per extcon instance.
|
||||||
|
|
||||||
|
config EXTCON_INTEL_INT3496
|
||||||
|
tristate "Intel INT3496 ACPI device extcon driver"
|
||||||
|
depends on GPIOLIB && ACPI
|
||||||
|
help
|
||||||
|
Say Y here to enable extcon support for USB OTG ports controlled by
|
||||||
|
an Intel INT3496 ACPI device.
|
||||||
|
|
||||||
|
This ACPI device is typically found on Intel Baytrail or Cherrytrail
|
||||||
|
based tablets, or other Baytrail / Cherrytrail devices.
|
||||||
|
|
||||||
config EXTCON_MAX14577
|
config EXTCON_MAX14577
|
||||||
tristate "Maxim MAX14577/77836 EXTCON Support"
|
tristate "Maxim MAX14577/77836 EXTCON Support"
|
||||||
depends on MFD_MAX14577
|
depends on MFD_MAX14577
|
||||||
|
|
|
@ -8,6 +8,7 @@ obj-$(CONFIG_EXTCON_ADC_JACK) += extcon-adc-jack.o
|
||||||
obj-$(CONFIG_EXTCON_ARIZONA) += extcon-arizona.o
|
obj-$(CONFIG_EXTCON_ARIZONA) += extcon-arizona.o
|
||||||
obj-$(CONFIG_EXTCON_AXP288) += extcon-axp288.o
|
obj-$(CONFIG_EXTCON_AXP288) += extcon-axp288.o
|
||||||
obj-$(CONFIG_EXTCON_GPIO) += extcon-gpio.o
|
obj-$(CONFIG_EXTCON_GPIO) += extcon-gpio.o
|
||||||
|
obj-$(CONFIG_EXTCON_INTEL_INT3496) += extcon-intel-int3496.o
|
||||||
obj-$(CONFIG_EXTCON_MAX14577) += extcon-max14577.o
|
obj-$(CONFIG_EXTCON_MAX14577) += extcon-max14577.o
|
||||||
obj-$(CONFIG_EXTCON_MAX3355) += extcon-max3355.o
|
obj-$(CONFIG_EXTCON_MAX3355) += extcon-max3355.o
|
||||||
obj-$(CONFIG_EXTCON_MAX77693) += extcon-max77693.o
|
obj-$(CONFIG_EXTCON_MAX77693) += extcon-max77693.o
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <linux/extcon.h>
|
#include "extcon.h"
|
||||||
|
|
||||||
static int devm_extcon_dev_match(struct device *dev, void *res, void *data)
|
static int devm_extcon_dev_match(struct device *dev, void *res, void *data)
|
||||||
{
|
{
|
||||||
|
|
|
@ -67,7 +67,7 @@ static void adc_jack_handler(struct work_struct *work)
|
||||||
|
|
||||||
ret = iio_read_channel_raw(data->chan, &adc_val);
|
ret = iio_read_channel_raw(data->chan, &adc_val);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
dev_err(&data->edev->dev, "read channel() error: %d\n", ret);
|
dev_err(data->dev, "read channel() error: %d\n", ret);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -236,10 +236,6 @@ static void arizona_extcon_set_mode(struct arizona_extcon_info *info, int mode)
|
||||||
|
|
||||||
mode %= info->micd_num_modes;
|
mode %= info->micd_num_modes;
|
||||||
|
|
||||||
if (arizona->pdata.micd_pol_gpio > 0)
|
|
||||||
gpio_set_value_cansleep(arizona->pdata.micd_pol_gpio,
|
|
||||||
info->micd_modes[mode].gpio);
|
|
||||||
else
|
|
||||||
gpiod_set_value_cansleep(info->micd_pol_gpio,
|
gpiod_set_value_cansleep(info->micd_pol_gpio,
|
||||||
info->micd_modes[mode].gpio);
|
info->micd_modes[mode].gpio);
|
||||||
|
|
||||||
|
@ -1412,21 +1408,21 @@ static int arizona_extcon_probe(struct platform_device *pdev)
|
||||||
regmap_update_bits(arizona->regmap, ARIZONA_GP_SWITCH_1,
|
regmap_update_bits(arizona->regmap, ARIZONA_GP_SWITCH_1,
|
||||||
ARIZONA_SW1_MODE_MASK, arizona->pdata.gpsw);
|
ARIZONA_SW1_MODE_MASK, arizona->pdata.gpsw);
|
||||||
|
|
||||||
if (arizona->pdata.micd_pol_gpio > 0) {
|
if (pdata->micd_pol_gpio > 0) {
|
||||||
if (info->micd_modes[0].gpio)
|
if (info->micd_modes[0].gpio)
|
||||||
mode = GPIOF_OUT_INIT_HIGH;
|
mode = GPIOF_OUT_INIT_HIGH;
|
||||||
else
|
else
|
||||||
mode = GPIOF_OUT_INIT_LOW;
|
mode = GPIOF_OUT_INIT_LOW;
|
||||||
|
|
||||||
ret = devm_gpio_request_one(&pdev->dev,
|
ret = devm_gpio_request_one(&pdev->dev, pdata->micd_pol_gpio,
|
||||||
arizona->pdata.micd_pol_gpio,
|
mode, "MICD polarity");
|
||||||
mode,
|
|
||||||
"MICD polarity");
|
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
dev_err(arizona->dev, "Failed to request GPIO%d: %d\n",
|
dev_err(arizona->dev, "Failed to request GPIO%d: %d\n",
|
||||||
arizona->pdata.micd_pol_gpio, ret);
|
pdata->micd_pol_gpio, ret);
|
||||||
goto err_register;
|
goto err_register;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
info->micd_pol_gpio = gpio_to_desc(pdata->micd_pol_gpio);
|
||||||
} else {
|
} else {
|
||||||
if (info->micd_modes[0].gpio)
|
if (info->micd_modes[0].gpio)
|
||||||
mode = GPIOD_OUT_HIGH;
|
mode = GPIOD_OUT_HIGH;
|
||||||
|
|
|
@ -21,7 +21,6 @@
|
||||||
#include <linux/interrupt.h>
|
#include <linux/interrupt.h>
|
||||||
#include <linux/platform_device.h>
|
#include <linux/platform_device.h>
|
||||||
#include <linux/property.h>
|
#include <linux/property.h>
|
||||||
#include <linux/usb/phy.h>
|
|
||||||
#include <linux/notifier.h>
|
#include <linux/notifier.h>
|
||||||
#include <linux/extcon.h>
|
#include <linux/extcon.h>
|
||||||
#include <linux/regmap.h>
|
#include <linux/regmap.h>
|
||||||
|
@ -71,12 +70,6 @@
|
||||||
#define DET_STAT_CDP 2
|
#define DET_STAT_CDP 2
|
||||||
#define DET_STAT_DCP 3
|
#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 {
|
enum axp288_extcon_reg {
|
||||||
AXP288_PS_STAT_REG = 0x00,
|
AXP288_PS_STAT_REG = 0x00,
|
||||||
AXP288_PS_BOOT_REASON_REG = 0x02,
|
AXP288_PS_BOOT_REASON_REG = 0x02,
|
||||||
|
@ -84,8 +77,6 @@ enum axp288_extcon_reg {
|
||||||
AXP288_BC_VBUS_CNTL_REG = 0x2d,
|
AXP288_BC_VBUS_CNTL_REG = 0x2d,
|
||||||
AXP288_BC_USB_STAT_REG = 0x2e,
|
AXP288_BC_USB_STAT_REG = 0x2e,
|
||||||
AXP288_BC_DET_STAT_REG = 0x2f,
|
AXP288_BC_DET_STAT_REG = 0x2f,
|
||||||
AXP288_PWRSRC_IRQ_CFG_REG = 0x40,
|
|
||||||
AXP288_BC12_IRQ_CFG_REG = 0x45,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
enum axp288_mux_select {
|
enum axp288_mux_select {
|
||||||
|
@ -105,6 +96,7 @@ static const unsigned int axp288_extcon_cables[] = {
|
||||||
EXTCON_CHG_USB_SDP,
|
EXTCON_CHG_USB_SDP,
|
||||||
EXTCON_CHG_USB_CDP,
|
EXTCON_CHG_USB_CDP,
|
||||||
EXTCON_CHG_USB_DCP,
|
EXTCON_CHG_USB_DCP,
|
||||||
|
EXTCON_USB,
|
||||||
EXTCON_NONE,
|
EXTCON_NONE,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -112,11 +104,11 @@ struct axp288_extcon_info {
|
||||||
struct device *dev;
|
struct device *dev;
|
||||||
struct regmap *regmap;
|
struct regmap *regmap;
|
||||||
struct regmap_irq_chip_data *regmap_irqc;
|
struct regmap_irq_chip_data *regmap_irqc;
|
||||||
struct axp288_extcon_pdata *pdata;
|
struct gpio_desc *gpio_mux_cntl;
|
||||||
int irq[EXTCON_IRQ_END];
|
int irq[EXTCON_IRQ_END];
|
||||||
struct extcon_dev *edev;
|
struct extcon_dev *edev;
|
||||||
struct notifier_block extcon_nb;
|
struct notifier_block extcon_nb;
|
||||||
struct usb_phy *otg;
|
unsigned int previous_cable;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Power up/down reason string array */
|
/* Power up/down reason string array */
|
||||||
|
@ -156,10 +148,9 @@ static void axp288_extcon_log_rsi(struct axp288_extcon_info *info)
|
||||||
|
|
||||||
static int axp288_handle_chrg_det_event(struct axp288_extcon_info *info)
|
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;
|
int ret, stat, cfg, pwr_stat;
|
||||||
u8 chrg_type;
|
u8 chrg_type;
|
||||||
|
unsigned int cable = info->previous_cable;
|
||||||
bool vbus_attach = false;
|
bool vbus_attach = false;
|
||||||
|
|
||||||
ret = regmap_read(info->regmap, AXP288_PS_STAT_REG, &pwr_stat);
|
ret = regmap_read(info->regmap, AXP288_PS_STAT_REG, &pwr_stat);
|
||||||
|
@ -168,9 +159,9 @@ static int axp288_handle_chrg_det_event(struct axp288_extcon_info *info)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
vbus_attach = (pwr_stat & PS_STAT_VBUS_PRESENT);
|
vbus_attach = (pwr_stat & PS_STAT_VBUS_VALID);
|
||||||
if (!vbus_attach)
|
if (!vbus_attach)
|
||||||
goto notify_otg;
|
goto no_vbus;
|
||||||
|
|
||||||
/* Check charger detection completion status */
|
/* Check charger detection completion status */
|
||||||
ret = regmap_read(info->regmap, AXP288_BC_GLOBAL_REG, &cfg);
|
ret = regmap_read(info->regmap, AXP288_BC_GLOBAL_REG, &cfg);
|
||||||
|
@ -190,19 +181,14 @@ static int axp288_handle_chrg_det_event(struct axp288_extcon_info *info)
|
||||||
switch (chrg_type) {
|
switch (chrg_type) {
|
||||||
case DET_STAT_SDP:
|
case DET_STAT_SDP:
|
||||||
dev_dbg(info->dev, "sdp cable is connected\n");
|
dev_dbg(info->dev, "sdp cable is connected\n");
|
||||||
notify_otg = true;
|
|
||||||
notify_charger = true;
|
|
||||||
cable = EXTCON_CHG_USB_SDP;
|
cable = EXTCON_CHG_USB_SDP;
|
||||||
break;
|
break;
|
||||||
case DET_STAT_CDP:
|
case DET_STAT_CDP:
|
||||||
dev_dbg(info->dev, "cdp cable is connected\n");
|
dev_dbg(info->dev, "cdp cable is connected\n");
|
||||||
notify_otg = true;
|
|
||||||
notify_charger = true;
|
|
||||||
cable = EXTCON_CHG_USB_CDP;
|
cable = EXTCON_CHG_USB_CDP;
|
||||||
break;
|
break;
|
||||||
case DET_STAT_DCP:
|
case DET_STAT_DCP:
|
||||||
dev_dbg(info->dev, "dcp cable is connected\n");
|
dev_dbg(info->dev, "dcp cable is connected\n");
|
||||||
notify_charger = true;
|
|
||||||
cable = EXTCON_CHG_USB_DCP;
|
cable = EXTCON_CHG_USB_DCP;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -210,27 +196,28 @@ static int axp288_handle_chrg_det_event(struct axp288_extcon_info *info)
|
||||||
"disconnect or unknown or ID event\n");
|
"disconnect or unknown or ID event\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
notify_otg:
|
no_vbus:
|
||||||
if (notify_otg) {
|
|
||||||
/*
|
/*
|
||||||
* If VBUS is absent Connect D+/D- lines to PMIC for BC
|
* If VBUS is absent Connect D+/D- lines to PMIC for BC
|
||||||
* detection. Else connect them to SOC for USB communication.
|
* detection. Else connect them to SOC for USB communication.
|
||||||
*/
|
*/
|
||||||
if (info->pdata->gpio_mux_cntl)
|
if (info->gpio_mux_cntl)
|
||||||
gpiod_set_value(info->pdata->gpio_mux_cntl,
|
gpiod_set_value(info->gpio_mux_cntl,
|
||||||
vbus_attach ? EXTCON_GPIO_MUX_SEL_SOC
|
vbus_attach ? EXTCON_GPIO_MUX_SEL_SOC
|
||||||
: EXTCON_GPIO_MUX_SEL_PMIC);
|
: EXTCON_GPIO_MUX_SEL_PMIC);
|
||||||
|
|
||||||
atomic_notifier_call_chain(&info->otg->notifier,
|
extcon_set_state_sync(info->edev, info->previous_cable, false);
|
||||||
vbus_attach ? USB_EVENT_VBUS : USB_EVENT_NONE, NULL);
|
if (info->previous_cable == EXTCON_CHG_USB_SDP)
|
||||||
}
|
extcon_set_state_sync(info->edev, EXTCON_USB, false);
|
||||||
|
|
||||||
if (notify_charger)
|
if (vbus_attach) {
|
||||||
extcon_set_state_sync(info->edev, cable, vbus_attach);
|
extcon_set_state_sync(info->edev, cable, vbus_attach);
|
||||||
|
if (cable == EXTCON_CHG_USB_SDP)
|
||||||
|
extcon_set_state_sync(info->edev, EXTCON_USB,
|
||||||
|
vbus_attach);
|
||||||
|
|
||||||
/* Clear the flags on disconnect event */
|
info->previous_cable = cable;
|
||||||
if (!vbus_attach)
|
}
|
||||||
notify_otg = notify_charger = false;
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
@ -253,15 +240,10 @@ static irqreturn_t axp288_extcon_isr(int irq, void *data)
|
||||||
return IRQ_HANDLED;
|
return IRQ_HANDLED;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void axp288_extcon_enable_irq(struct axp288_extcon_info *info)
|
static void axp288_extcon_enable(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,
|
regmap_update_bits(info->regmap, AXP288_BC_GLOBAL_REG,
|
||||||
BC_GLOBAL_RUN, 0);
|
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 */
|
/* Enable the charger detection logic */
|
||||||
regmap_update_bits(info->regmap, AXP288_BC_GLOBAL_REG,
|
regmap_update_bits(info->regmap, AXP288_BC_GLOBAL_REG,
|
||||||
BC_GLOBAL_RUN, BC_GLOBAL_RUN);
|
BC_GLOBAL_RUN, BC_GLOBAL_RUN);
|
||||||
|
@ -271,6 +253,7 @@ static int axp288_extcon_probe(struct platform_device *pdev)
|
||||||
{
|
{
|
||||||
struct axp288_extcon_info *info;
|
struct axp288_extcon_info *info;
|
||||||
struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent);
|
struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent);
|
||||||
|
struct axp288_extcon_pdata *pdata = pdev->dev.platform_data;
|
||||||
int ret, i, pirq, gpio;
|
int ret, i, pirq, gpio;
|
||||||
|
|
||||||
info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
|
info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
|
||||||
|
@ -280,15 +263,10 @@ static int axp288_extcon_probe(struct platform_device *pdev)
|
||||||
info->dev = &pdev->dev;
|
info->dev = &pdev->dev;
|
||||||
info->regmap = axp20x->regmap;
|
info->regmap = axp20x->regmap;
|
||||||
info->regmap_irqc = axp20x->regmap_irqc;
|
info->regmap_irqc = axp20x->regmap_irqc;
|
||||||
info->pdata = pdev->dev.platform_data;
|
info->previous_cable = EXTCON_NONE;
|
||||||
|
if (pdata)
|
||||||
|
info->gpio_mux_cntl = pdata->gpio_mux_cntl;
|
||||||
|
|
||||||
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);
|
platform_set_drvdata(pdev, info);
|
||||||
|
|
||||||
axp288_extcon_log_rsi(info);
|
axp288_extcon_log_rsi(info);
|
||||||
|
@ -308,23 +286,16 @@ static int axp288_extcon_probe(struct platform_device *pdev)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Get otg transceiver phy */
|
|
||||||
info->otg = devm_usb_get_phy(&pdev->dev, 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 */
|
/* Set up gpio control for USB Mux */
|
||||||
if (info->pdata->gpio_mux_cntl) {
|
if (info->gpio_mux_cntl) {
|
||||||
gpio = desc_to_gpio(info->pdata->gpio_mux_cntl);
|
gpio = desc_to_gpio(info->gpio_mux_cntl);
|
||||||
ret = devm_gpio_request(&pdev->dev, gpio, "USB_MUX");
|
ret = devm_gpio_request(&pdev->dev, gpio, "USB_MUX");
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
dev_err(&pdev->dev,
|
dev_err(&pdev->dev,
|
||||||
"failed to request the gpio=%d\n", gpio);
|
"failed to request the gpio=%d\n", gpio);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
gpiod_direction_output(info->pdata->gpio_mux_cntl,
|
gpiod_direction_output(info->gpio_mux_cntl,
|
||||||
EXTCON_GPIO_MUX_SEL_PMIC);
|
EXTCON_GPIO_MUX_SEL_PMIC);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -349,14 +320,21 @@ static int axp288_extcon_probe(struct platform_device *pdev)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Enable interrupts */
|
/* Start charger cable type detection */
|
||||||
axp288_extcon_enable_irq(info);
|
axp288_extcon_enable(info);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const struct platform_device_id axp288_extcon_table[] = {
|
||||||
|
{ .name = "axp288_extcon" },
|
||||||
|
{},
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(platform, axp288_extcon_table);
|
||||||
|
|
||||||
static struct platform_driver axp288_extcon_driver = {
|
static struct platform_driver axp288_extcon_driver = {
|
||||||
.probe = axp288_extcon_probe,
|
.probe = axp288_extcon_probe,
|
||||||
|
.id_table = axp288_extcon_table,
|
||||||
.driver = {
|
.driver = {
|
||||||
.name = "axp288_extcon",
|
.name = "axp288_extcon",
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,179 @@
|
||||||
|
/*
|
||||||
|
* Intel INT3496 ACPI device extcon driver
|
||||||
|
*
|
||||||
|
* Copyright (c) 2016 Hans de Goede <hdegoede@redhat.com>
|
||||||
|
*
|
||||||
|
* Based on android x86 kernel code which is:
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014, Intel Corporation.
|
||||||
|
* Author: David Cohen <david.a.cohen@linux.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/acpi.h>
|
||||||
|
#include <linux/extcon.h>
|
||||||
|
#include <linux/gpio.h>
|
||||||
|
#include <linux/interrupt.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/platform_device.h>
|
||||||
|
|
||||||
|
#define INT3496_GPIO_USB_ID 0
|
||||||
|
#define INT3496_GPIO_VBUS_EN 1
|
||||||
|
#define INT3496_GPIO_USB_MUX 2
|
||||||
|
#define DEBOUNCE_TIME msecs_to_jiffies(50)
|
||||||
|
|
||||||
|
struct int3496_data {
|
||||||
|
struct device *dev;
|
||||||
|
struct extcon_dev *edev;
|
||||||
|
struct delayed_work work;
|
||||||
|
struct gpio_desc *gpio_usb_id;
|
||||||
|
struct gpio_desc *gpio_vbus_en;
|
||||||
|
struct gpio_desc *gpio_usb_mux;
|
||||||
|
int usb_id_irq;
|
||||||
|
};
|
||||||
|
|
||||||
|
static const unsigned int int3496_cable[] = {
|
||||||
|
EXTCON_USB_HOST,
|
||||||
|
EXTCON_NONE,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void int3496_do_usb_id(struct work_struct *work)
|
||||||
|
{
|
||||||
|
struct int3496_data *data =
|
||||||
|
container_of(work, struct int3496_data, work.work);
|
||||||
|
int id = gpiod_get_value_cansleep(data->gpio_usb_id);
|
||||||
|
|
||||||
|
/* id == 1: PERIPHERAL, id == 0: HOST */
|
||||||
|
dev_dbg(data->dev, "Connected %s cable\n", id ? "PERIPHERAL" : "HOST");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Peripheral: set USB mux to peripheral and disable VBUS
|
||||||
|
* Host: set USB mux to host and enable VBUS
|
||||||
|
*/
|
||||||
|
if (!IS_ERR(data->gpio_usb_mux))
|
||||||
|
gpiod_direction_output(data->gpio_usb_mux, id);
|
||||||
|
|
||||||
|
if (!IS_ERR(data->gpio_vbus_en))
|
||||||
|
gpiod_direction_output(data->gpio_vbus_en, !id);
|
||||||
|
|
||||||
|
extcon_set_state_sync(data->edev, EXTCON_USB_HOST, !id);
|
||||||
|
}
|
||||||
|
|
||||||
|
static irqreturn_t int3496_thread_isr(int irq, void *priv)
|
||||||
|
{
|
||||||
|
struct int3496_data *data = priv;
|
||||||
|
|
||||||
|
/* Let the pin settle before processing it */
|
||||||
|
mod_delayed_work(system_wq, &data->work, DEBOUNCE_TIME);
|
||||||
|
|
||||||
|
return IRQ_HANDLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int int3496_probe(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct device *dev = &pdev->dev;
|
||||||
|
struct int3496_data *data;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
|
||||||
|
if (!data)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
data->dev = dev;
|
||||||
|
INIT_DELAYED_WORK(&data->work, int3496_do_usb_id);
|
||||||
|
|
||||||
|
data->gpio_usb_id = devm_gpiod_get_index(dev, "id",
|
||||||
|
INT3496_GPIO_USB_ID,
|
||||||
|
GPIOD_IN);
|
||||||
|
if (IS_ERR(data->gpio_usb_id)) {
|
||||||
|
ret = PTR_ERR(data->gpio_usb_id);
|
||||||
|
dev_err(dev, "can't request USB ID GPIO: %d\n", ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
data->usb_id_irq = gpiod_to_irq(data->gpio_usb_id);
|
||||||
|
if (data->usb_id_irq <= 0) {
|
||||||
|
dev_err(dev, "can't get USB ID IRQ: %d\n", data->usb_id_irq);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
data->gpio_vbus_en = devm_gpiod_get_index(dev, "vbus en",
|
||||||
|
INT3496_GPIO_VBUS_EN,
|
||||||
|
GPIOD_ASIS);
|
||||||
|
if (IS_ERR(data->gpio_vbus_en))
|
||||||
|
dev_info(dev, "can't request VBUS EN GPIO\n");
|
||||||
|
|
||||||
|
data->gpio_usb_mux = devm_gpiod_get_index(dev, "usb mux",
|
||||||
|
INT3496_GPIO_USB_MUX,
|
||||||
|
GPIOD_ASIS);
|
||||||
|
if (IS_ERR(data->gpio_usb_mux))
|
||||||
|
dev_info(dev, "can't request USB MUX GPIO\n");
|
||||||
|
|
||||||
|
/* register extcon device */
|
||||||
|
data->edev = devm_extcon_dev_allocate(dev, int3496_cable);
|
||||||
|
if (IS_ERR(data->edev))
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
ret = devm_extcon_dev_register(dev, data->edev);
|
||||||
|
if (ret < 0) {
|
||||||
|
dev_err(dev, "can't register extcon device: %d\n", ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = devm_request_threaded_irq(dev, data->usb_id_irq,
|
||||||
|
NULL, int3496_thread_isr,
|
||||||
|
IRQF_SHARED | IRQF_ONESHOT |
|
||||||
|
IRQF_TRIGGER_RISING |
|
||||||
|
IRQF_TRIGGER_FALLING,
|
||||||
|
dev_name(dev), data);
|
||||||
|
if (ret < 0) {
|
||||||
|
dev_err(dev, "can't request IRQ for USB ID GPIO: %d\n", ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* queue initial processing of id-pin */
|
||||||
|
queue_delayed_work(system_wq, &data->work, 0);
|
||||||
|
|
||||||
|
platform_set_drvdata(pdev, data);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int int3496_remove(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct int3496_data *data = platform_get_drvdata(pdev);
|
||||||
|
|
||||||
|
devm_free_irq(&pdev->dev, data->usb_id_irq, data);
|
||||||
|
cancel_delayed_work_sync(&data->work);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct acpi_device_id int3496_acpi_match[] = {
|
||||||
|
{ "INT3496" },
|
||||||
|
{ }
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(acpi, int3496_acpi_match);
|
||||||
|
|
||||||
|
static struct platform_driver int3496_driver = {
|
||||||
|
.driver = {
|
||||||
|
.name = "intel-int3496",
|
||||||
|
.acpi_match_table = int3496_acpi_match,
|
||||||
|
},
|
||||||
|
.probe = int3496_probe,
|
||||||
|
.remove = int3496_remove,
|
||||||
|
};
|
||||||
|
|
||||||
|
module_platform_driver(int3496_driver);
|
||||||
|
|
||||||
|
MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
|
||||||
|
MODULE_DESCRIPTION("Intel INT3496 ACPI device extcon driver");
|
||||||
|
MODULE_LICENSE("GPL");
|
|
@ -531,8 +531,10 @@ static int max14577_parse_irq(struct max14577_muic_info *info, int irq_type)
|
||||||
case MAX14577_IRQ_INT1_ADC:
|
case MAX14577_IRQ_INT1_ADC:
|
||||||
case MAX14577_IRQ_INT1_ADCLOW:
|
case MAX14577_IRQ_INT1_ADCLOW:
|
||||||
case MAX14577_IRQ_INT1_ADCERR:
|
case MAX14577_IRQ_INT1_ADCERR:
|
||||||
/* Handle all of accessory except for
|
/*
|
||||||
type of charger accessory */
|
* Handle all of accessory except for
|
||||||
|
* type of charger accessory.
|
||||||
|
*/
|
||||||
info->irq_adc = true;
|
info->irq_adc = true;
|
||||||
return 1;
|
return 1;
|
||||||
case MAX14577_IRQ_INT2_CHGTYP:
|
case MAX14577_IRQ_INT2_CHGTYP:
|
||||||
|
|
|
@ -188,8 +188,10 @@ enum max77693_muic_acc_type {
|
||||||
MAX77693_MUIC_ADC_AUDIO_MODE_REMOTE,
|
MAX77693_MUIC_ADC_AUDIO_MODE_REMOTE,
|
||||||
MAX77693_MUIC_ADC_OPEN,
|
MAX77693_MUIC_ADC_OPEN,
|
||||||
|
|
||||||
/* The below accessories have same ADC value so ADCLow and
|
/*
|
||||||
ADC1K bit is used to separate specific accessory */
|
* The below accessories have same ADC value so ADCLow and
|
||||||
|
* ADC1K bit is used to separate specific accessory.
|
||||||
|
*/
|
||||||
/* ADC|VBVolot|ADCLow|ADC1K| */
|
/* ADC|VBVolot|ADCLow|ADC1K| */
|
||||||
MAX77693_MUIC_GND_USB_HOST = 0x100, /* 0x0| 0| 0| 0| */
|
MAX77693_MUIC_GND_USB_HOST = 0x100, /* 0x0| 0| 0| 0| */
|
||||||
MAX77693_MUIC_GND_USB_HOST_VB = 0x104, /* 0x0| 1| 0| 0| */
|
MAX77693_MUIC_GND_USB_HOST_VB = 0x104, /* 0x0| 1| 0| 0| */
|
||||||
|
@ -970,8 +972,10 @@ static void max77693_muic_irq_work(struct work_struct *work)
|
||||||
case MAX77693_MUIC_IRQ_INT1_ADC_LOW:
|
case MAX77693_MUIC_IRQ_INT1_ADC_LOW:
|
||||||
case MAX77693_MUIC_IRQ_INT1_ADC_ERR:
|
case MAX77693_MUIC_IRQ_INT1_ADC_ERR:
|
||||||
case MAX77693_MUIC_IRQ_INT1_ADC1K:
|
case MAX77693_MUIC_IRQ_INT1_ADC1K:
|
||||||
/* Handle all of accessory except for
|
/*
|
||||||
type of charger accessory */
|
* Handle all of accessory except for
|
||||||
|
* type of charger accessory.
|
||||||
|
*/
|
||||||
ret = max77693_muic_adc_handler(info);
|
ret = max77693_muic_adc_handler(info);
|
||||||
break;
|
break;
|
||||||
case MAX77693_MUIC_IRQ_INT2_CHGTYP:
|
case MAX77693_MUIC_IRQ_INT2_CHGTYP:
|
||||||
|
|
|
@ -97,8 +97,10 @@ enum max77843_muic_accessory_type {
|
||||||
MAX77843_MUIC_ADC_AUDIO_DEVICE_TYPE1,
|
MAX77843_MUIC_ADC_AUDIO_DEVICE_TYPE1,
|
||||||
MAX77843_MUIC_ADC_OPEN,
|
MAX77843_MUIC_ADC_OPEN,
|
||||||
|
|
||||||
/* The blow accessories should check
|
/*
|
||||||
not only ADC value but also ADC1K and VBVolt value. */
|
* The below accessories should check
|
||||||
|
* not only ADC value but also ADC1K and VBVolt value.
|
||||||
|
*/
|
||||||
/* Offset|ADC1K|VBVolt| */
|
/* Offset|ADC1K|VBVolt| */
|
||||||
MAX77843_MUIC_GND_USB_HOST = 0x100, /* 0x1| 0| 0| */
|
MAX77843_MUIC_GND_USB_HOST = 0x100, /* 0x1| 0| 0| */
|
||||||
MAX77843_MUIC_GND_USB_HOST_VB = 0x101, /* 0x1| 0| 1| */
|
MAX77843_MUIC_GND_USB_HOST_VB = 0x101, /* 0x1| 0| 1| */
|
||||||
|
@ -265,16 +267,20 @@ static int max77843_muic_get_cable_type(struct max77843_muic_info *info,
|
||||||
/* Check GROUND accessory with charger cable */
|
/* Check GROUND accessory with charger cable */
|
||||||
if (adc == MAX77843_MUIC_ADC_GROUND) {
|
if (adc == MAX77843_MUIC_ADC_GROUND) {
|
||||||
if (chg_type == MAX77843_MUIC_CHG_NONE) {
|
if (chg_type == MAX77843_MUIC_CHG_NONE) {
|
||||||
/* The following state when charger cable is
|
/*
|
||||||
|
* The following state when charger cable is
|
||||||
* disconnected but the GROUND accessory still
|
* disconnected but the GROUND accessory still
|
||||||
* connected */
|
* connected.
|
||||||
|
*/
|
||||||
*attached = false;
|
*attached = false;
|
||||||
cable_type = info->prev_chg_type;
|
cable_type = info->prev_chg_type;
|
||||||
info->prev_chg_type = MAX77843_MUIC_CHG_NONE;
|
info->prev_chg_type = MAX77843_MUIC_CHG_NONE;
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
/* The following state when charger cable is
|
/*
|
||||||
* connected on the GROUND accessory */
|
* The following state when charger cable is
|
||||||
|
* connected on the GROUND accessory.
|
||||||
|
*/
|
||||||
*attached = true;
|
*attached = true;
|
||||||
cable_type = MAX77843_MUIC_CHG_GND;
|
cable_type = MAX77843_MUIC_CHG_GND;
|
||||||
info->prev_chg_type = MAX77843_MUIC_CHG_GND;
|
info->prev_chg_type = MAX77843_MUIC_CHG_GND;
|
||||||
|
@ -299,11 +305,13 @@ static int max77843_muic_get_cable_type(struct max77843_muic_info *info,
|
||||||
} else {
|
} else {
|
||||||
*attached = true;
|
*attached = true;
|
||||||
|
|
||||||
/* Offset|ADC1K|VBVolt|
|
/*
|
||||||
|
* Offset|ADC1K|VBVolt|
|
||||||
* 0x1| 0| 0| USB-HOST
|
* 0x1| 0| 0| USB-HOST
|
||||||
* 0x1| 0| 1| USB-HOST with VB
|
* 0x1| 0| 1| USB-HOST with VB
|
||||||
* 0x1| 1| 0| MHL
|
* 0x1| 1| 0| MHL
|
||||||
* 0x1| 1| 1| MHL with VB */
|
* 0x1| 1| 1| MHL with VB
|
||||||
|
*/
|
||||||
/* Get ADC1K register bit */
|
/* Get ADC1K register bit */
|
||||||
gnd_type = (info->status[MAX77843_MUIC_STATUS1] &
|
gnd_type = (info->status[MAX77843_MUIC_STATUS1] &
|
||||||
MAX77843_MUIC_STATUS1_ADC1K_MASK);
|
MAX77843_MUIC_STATUS1_ADC1K_MASK);
|
||||||
|
|
|
@ -62,7 +62,7 @@ static irqreturn_t palmas_vbus_irq_handler(int irq, void *_palmas_usb)
|
||||||
if (palmas_usb->linkstat != PALMAS_USB_STATE_VBUS) {
|
if (palmas_usb->linkstat != PALMAS_USB_STATE_VBUS) {
|
||||||
palmas_usb->linkstat = PALMAS_USB_STATE_VBUS;
|
palmas_usb->linkstat = PALMAS_USB_STATE_VBUS;
|
||||||
extcon_set_state_sync(edev, EXTCON_USB, true);
|
extcon_set_state_sync(edev, EXTCON_USB, true);
|
||||||
dev_info(palmas_usb->dev, "USB cable is attached\n");
|
dev_dbg(palmas_usb->dev, "USB cable is attached\n");
|
||||||
} else {
|
} else {
|
||||||
dev_dbg(palmas_usb->dev,
|
dev_dbg(palmas_usb->dev,
|
||||||
"Spurious connect event detected\n");
|
"Spurious connect event detected\n");
|
||||||
|
@ -71,7 +71,7 @@ static irqreturn_t palmas_vbus_irq_handler(int irq, void *_palmas_usb)
|
||||||
if (palmas_usb->linkstat == PALMAS_USB_STATE_VBUS) {
|
if (palmas_usb->linkstat == PALMAS_USB_STATE_VBUS) {
|
||||||
palmas_usb->linkstat = PALMAS_USB_STATE_DISCONNECT;
|
palmas_usb->linkstat = PALMAS_USB_STATE_DISCONNECT;
|
||||||
extcon_set_state_sync(edev, EXTCON_USB, false);
|
extcon_set_state_sync(edev, EXTCON_USB, false);
|
||||||
dev_info(palmas_usb->dev, "USB cable is detached\n");
|
dev_dbg(palmas_usb->dev, "USB cable is detached\n");
|
||||||
} else {
|
} else {
|
||||||
dev_dbg(palmas_usb->dev,
|
dev_dbg(palmas_usb->dev,
|
||||||
"Spurious disconnect event detected\n");
|
"Spurious disconnect event detected\n");
|
||||||
|
@ -99,7 +99,7 @@ static irqreturn_t palmas_id_irq_handler(int irq, void *_palmas_usb)
|
||||||
PALMAS_USB_ID_INT_EN_HI_CLR_ID_GND);
|
PALMAS_USB_ID_INT_EN_HI_CLR_ID_GND);
|
||||||
palmas_usb->linkstat = PALMAS_USB_STATE_ID;
|
palmas_usb->linkstat = PALMAS_USB_STATE_ID;
|
||||||
extcon_set_state_sync(edev, EXTCON_USB_HOST, true);
|
extcon_set_state_sync(edev, EXTCON_USB_HOST, true);
|
||||||
dev_info(palmas_usb->dev, "USB-HOST cable is attached\n");
|
dev_dbg(palmas_usb->dev, "USB-HOST cable is attached\n");
|
||||||
} else if ((set & PALMAS_USB_ID_INT_SRC_ID_FLOAT) &&
|
} else if ((set & PALMAS_USB_ID_INT_SRC_ID_FLOAT) &&
|
||||||
(id_src & PALMAS_USB_ID_INT_SRC_ID_FLOAT)) {
|
(id_src & PALMAS_USB_ID_INT_SRC_ID_FLOAT)) {
|
||||||
palmas_write(palmas_usb->palmas, PALMAS_USB_OTG_BASE,
|
palmas_write(palmas_usb->palmas, PALMAS_USB_OTG_BASE,
|
||||||
|
@ -107,17 +107,17 @@ static irqreturn_t palmas_id_irq_handler(int irq, void *_palmas_usb)
|
||||||
PALMAS_USB_ID_INT_EN_HI_CLR_ID_FLOAT);
|
PALMAS_USB_ID_INT_EN_HI_CLR_ID_FLOAT);
|
||||||
palmas_usb->linkstat = PALMAS_USB_STATE_DISCONNECT;
|
palmas_usb->linkstat = PALMAS_USB_STATE_DISCONNECT;
|
||||||
extcon_set_state_sync(edev, EXTCON_USB_HOST, false);
|
extcon_set_state_sync(edev, EXTCON_USB_HOST, false);
|
||||||
dev_info(palmas_usb->dev, "USB-HOST cable is detached\n");
|
dev_dbg(palmas_usb->dev, "USB-HOST cable is detached\n");
|
||||||
} else if ((palmas_usb->linkstat == PALMAS_USB_STATE_ID) &&
|
} else if ((palmas_usb->linkstat == PALMAS_USB_STATE_ID) &&
|
||||||
(!(set & PALMAS_USB_ID_INT_SRC_ID_GND))) {
|
(!(set & PALMAS_USB_ID_INT_SRC_ID_GND))) {
|
||||||
palmas_usb->linkstat = PALMAS_USB_STATE_DISCONNECT;
|
palmas_usb->linkstat = PALMAS_USB_STATE_DISCONNECT;
|
||||||
extcon_set_state_sync(edev, EXTCON_USB_HOST, false);
|
extcon_set_state_sync(edev, EXTCON_USB_HOST, false);
|
||||||
dev_info(palmas_usb->dev, "USB-HOST cable is detached\n");
|
dev_dbg(palmas_usb->dev, "USB-HOST cable is detached\n");
|
||||||
} else if ((palmas_usb->linkstat == PALMAS_USB_STATE_DISCONNECT) &&
|
} else if ((palmas_usb->linkstat == PALMAS_USB_STATE_DISCONNECT) &&
|
||||||
(id_src & PALMAS_USB_ID_INT_SRC_ID_GND)) {
|
(id_src & PALMAS_USB_ID_INT_SRC_ID_GND)) {
|
||||||
palmas_usb->linkstat = PALMAS_USB_STATE_ID;
|
palmas_usb->linkstat = PALMAS_USB_STATE_ID;
|
||||||
extcon_set_state_sync(edev, EXTCON_USB_HOST, true);
|
extcon_set_state_sync(edev, EXTCON_USB_HOST, true);
|
||||||
dev_info(palmas_usb->dev, " USB-HOST cable is attached\n");
|
dev_dbg(palmas_usb->dev, " USB-HOST cable is attached\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
return IRQ_HANDLED;
|
return IRQ_HANDLED;
|
||||||
|
@ -138,10 +138,10 @@ static void palmas_gpio_id_detect(struct work_struct *work)
|
||||||
|
|
||||||
if (id) {
|
if (id) {
|
||||||
extcon_set_state_sync(edev, EXTCON_USB_HOST, false);
|
extcon_set_state_sync(edev, EXTCON_USB_HOST, false);
|
||||||
dev_info(palmas_usb->dev, "USB-HOST cable is detached\n");
|
dev_dbg(palmas_usb->dev, "USB-HOST cable is detached\n");
|
||||||
} else {
|
} else {
|
||||||
extcon_set_state_sync(edev, EXTCON_USB_HOST, true);
|
extcon_set_state_sync(edev, EXTCON_USB_HOST, true);
|
||||||
dev_info(palmas_usb->dev, "USB-HOST cable is attached\n");
|
dev_dbg(palmas_usb->dev, "USB-HOST cable is attached\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,6 +190,11 @@ static int palmas_usb_probe(struct platform_device *pdev)
|
||||||
struct palmas_usb *palmas_usb;
|
struct palmas_usb *palmas_usb;
|
||||||
int status;
|
int status;
|
||||||
|
|
||||||
|
if (!palmas) {
|
||||||
|
dev_err(&pdev->dev, "failed to get valid parent\n");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
palmas_usb = devm_kzalloc(&pdev->dev, sizeof(*palmas_usb), GFP_KERNEL);
|
palmas_usb = devm_kzalloc(&pdev->dev, sizeof(*palmas_usb), GFP_KERNEL);
|
||||||
if (!palmas_usb)
|
if (!palmas_usb)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
|
|
@ -142,8 +142,10 @@ enum rt8973a_muic_acc_type {
|
||||||
RT8973A_MUIC_ADC_UNKNOWN_ACC_5,
|
RT8973A_MUIC_ADC_UNKNOWN_ACC_5,
|
||||||
RT8973A_MUIC_ADC_OPEN = 0x1f,
|
RT8973A_MUIC_ADC_OPEN = 0x1f,
|
||||||
|
|
||||||
/* The below accessories has same ADC value (0x1f).
|
/*
|
||||||
So, Device type1 is used to separate specific accessory. */
|
* The below accessories has same ADC value (0x1f).
|
||||||
|
* So, Device type1 is used to separate specific accessory.
|
||||||
|
*/
|
||||||
/* |---------|--ADC| */
|
/* |---------|--ADC| */
|
||||||
/* | [7:5]|[4:0]| */
|
/* | [7:5]|[4:0]| */
|
||||||
RT8973A_MUIC_ADC_USB = 0x3f, /* | 001|11111| */
|
RT8973A_MUIC_ADC_USB = 0x3f, /* | 001|11111| */
|
||||||
|
|
|
@ -135,8 +135,10 @@ enum sm5502_muic_acc_type {
|
||||||
SM5502_MUIC_ADC_AUDIO_TYPE1,
|
SM5502_MUIC_ADC_AUDIO_TYPE1,
|
||||||
SM5502_MUIC_ADC_OPEN = 0x1f,
|
SM5502_MUIC_ADC_OPEN = 0x1f,
|
||||||
|
|
||||||
/* The below accessories have same ADC value (0x1f or 0x1e).
|
/*
|
||||||
So, Device type1 is used to separate specific accessory. */
|
* The below accessories have same ADC value (0x1f or 0x1e).
|
||||||
|
* So, Device type1 is used to separate specific accessory.
|
||||||
|
*/
|
||||||
/* |---------|--ADC| */
|
/* |---------|--ADC| */
|
||||||
/* | [7:5]|[4:0]| */
|
/* | [7:5]|[4:0]| */
|
||||||
SM5502_MUIC_ADC_AUDIO_TYPE1_FULL_REMOTE = 0x3e, /* | 001|11110| */
|
SM5502_MUIC_ADC_AUDIO_TYPE1_FULL_REMOTE = 0x3e, /* | 001|11110| */
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
#include <linux/workqueue.h>
|
#include <linux/workqueue.h>
|
||||||
#include <linux/acpi.h>
|
#include <linux/acpi.h>
|
||||||
|
#include <linux/pinctrl/consumer.h>
|
||||||
|
|
||||||
#define USB_GPIO_DEBOUNCE_MS 20 /* ms */
|
#define USB_GPIO_DEBOUNCE_MS 20 /* ms */
|
||||||
|
|
||||||
|
@ -245,6 +246,9 @@ static int usb_extcon_suspend(struct device *dev)
|
||||||
if (info->vbus_gpiod)
|
if (info->vbus_gpiod)
|
||||||
disable_irq(info->vbus_irq);
|
disable_irq(info->vbus_irq);
|
||||||
|
|
||||||
|
if (!device_may_wakeup(dev))
|
||||||
|
pinctrl_pm_select_sleep_state(dev);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -253,6 +257,9 @@ static int usb_extcon_resume(struct device *dev)
|
||||||
struct usb_extcon_info *info = dev_get_drvdata(dev);
|
struct usb_extcon_info *info = dev_get_drvdata(dev);
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
|
|
||||||
|
if (!device_may_wakeup(dev))
|
||||||
|
pinctrl_pm_select_default_state(dev);
|
||||||
|
|
||||||
if (device_may_wakeup(dev)) {
|
if (device_may_wakeup(dev)) {
|
||||||
if (info->id_gpiod) {
|
if (info->id_gpiod) {
|
||||||
ret = disable_irq_wake(info->id_irq);
|
ret = disable_irq_wake(info->id_irq);
|
||||||
|
|
|
@ -30,11 +30,12 @@
|
||||||
#include <linux/device.h>
|
#include <linux/device.h>
|
||||||
#include <linux/fs.h>
|
#include <linux/fs.h>
|
||||||
#include <linux/err.h>
|
#include <linux/err.h>
|
||||||
#include <linux/extcon.h>
|
|
||||||
#include <linux/of.h>
|
#include <linux/of.h>
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
#include <linux/sysfs.h>
|
#include <linux/sysfs.h>
|
||||||
|
|
||||||
|
#include "extcon.h"
|
||||||
|
|
||||||
#define SUPPORTED_CABLE_MAX 32
|
#define SUPPORTED_CABLE_MAX 32
|
||||||
#define CABLE_NAME_MAX 30
|
#define CABLE_NAME_MAX 30
|
||||||
|
|
||||||
|
@ -59,7 +60,7 @@ struct __extcon_info {
|
||||||
[EXTCON_USB_HOST] = {
|
[EXTCON_USB_HOST] = {
|
||||||
.type = EXTCON_TYPE_USB,
|
.type = EXTCON_TYPE_USB,
|
||||||
.id = EXTCON_USB_HOST,
|
.id = EXTCON_USB_HOST,
|
||||||
.name = "USB_HOST",
|
.name = "USB-HOST",
|
||||||
},
|
},
|
||||||
|
|
||||||
/* Charging external connector */
|
/* Charging external connector */
|
||||||
|
@ -98,6 +99,11 @@ struct __extcon_info {
|
||||||
.id = EXTCON_CHG_WPT,
|
.id = EXTCON_CHG_WPT,
|
||||||
.name = "WPT",
|
.name = "WPT",
|
||||||
},
|
},
|
||||||
|
[EXTCON_CHG_USB_PD] = {
|
||||||
|
.type = EXTCON_TYPE_CHG | EXTCON_TYPE_USB,
|
||||||
|
.id = EXTCON_CHG_USB_PD,
|
||||||
|
.name = "PD",
|
||||||
|
},
|
||||||
|
|
||||||
/* Jack external connector */
|
/* Jack external connector */
|
||||||
[EXTCON_JACK_MICROPHONE] = {
|
[EXTCON_JACK_MICROPHONE] = {
|
||||||
|
@ -906,10 +912,9 @@ int extcon_register_notifier(struct extcon_dev *edev, unsigned int id,
|
||||||
unsigned long flags;
|
unsigned long flags;
|
||||||
int ret, idx = -EINVAL;
|
int ret, idx = -EINVAL;
|
||||||
|
|
||||||
if (!nb)
|
if (!edev || !nb)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
if (edev) {
|
|
||||||
idx = find_cable_index_by_id(edev, id);
|
idx = find_cable_index_by_id(edev, id);
|
||||||
if (idx < 0)
|
if (idx < 0)
|
||||||
return idx;
|
return idx;
|
||||||
|
@ -917,24 +922,6 @@ int extcon_register_notifier(struct extcon_dev *edev, unsigned int id,
|
||||||
spin_lock_irqsave(&edev->lock, flags);
|
spin_lock_irqsave(&edev->lock, flags);
|
||||||
ret = raw_notifier_chain_register(&edev->nh[idx], nb);
|
ret = raw_notifier_chain_register(&edev->nh[idx], nb);
|
||||||
spin_unlock_irqrestore(&edev->lock, flags);
|
spin_unlock_irqrestore(&edev->lock, flags);
|
||||||
} else {
|
|
||||||
struct extcon_dev *extd;
|
|
||||||
|
|
||||||
mutex_lock(&extcon_dev_list_lock);
|
|
||||||
list_for_each_entry(extd, &extcon_dev_list, entry) {
|
|
||||||
idx = find_cable_index_by_id(extd, id);
|
|
||||||
if (idx >= 0)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
mutex_unlock(&extcon_dev_list_lock);
|
|
||||||
|
|
||||||
if (idx >= 0) {
|
|
||||||
edev = extd;
|
|
||||||
return extcon_register_notifier(extd, id, nb);
|
|
||||||
} else {
|
|
||||||
ret = -ENODEV;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
#ifndef __LINUX_EXTCON_INTERNAL_H__
|
||||||
|
#define __LINUX_EXTCON_INTERNAL_H__
|
||||||
|
|
||||||
|
#include <linux/extcon.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* struct extcon_dev - An extcon device represents one external connector.
|
||||||
|
* @name: The name of this extcon device. Parent device name is
|
||||||
|
* used if NULL.
|
||||||
|
* @supported_cable: Array of supported cable names ending with EXTCON_NONE.
|
||||||
|
* If supported_cable is NULL, cable name related APIs
|
||||||
|
* are disabled.
|
||||||
|
* @mutually_exclusive: Array of mutually exclusive set of cables that cannot
|
||||||
|
* be attached simultaneously. The array should be
|
||||||
|
* ending with NULL or be NULL (no mutually exclusive
|
||||||
|
* cables). For example, if it is { 0x7, 0x30, 0}, then,
|
||||||
|
* {0, 1}, {0, 1, 2}, {0, 2}, {1, 2}, or {4, 5} cannot
|
||||||
|
* be attached simulataneously. {0x7, 0} is equivalent to
|
||||||
|
* {0x3, 0x6, 0x5, 0}. If it is {0xFFFFFFFF, 0}, there
|
||||||
|
* can be no simultaneous connections.
|
||||||
|
* @dev: Device of this extcon.
|
||||||
|
* @state: Attach/detach state of this extcon. Do not provide at
|
||||||
|
* register-time.
|
||||||
|
* @nh: Notifier for the state change events from this extcon
|
||||||
|
* @entry: To support list of extcon devices so that users can
|
||||||
|
* search for extcon devices based on the extcon name.
|
||||||
|
* @lock:
|
||||||
|
* @max_supported: Internal value to store the number of cables.
|
||||||
|
* @extcon_dev_type: Device_type struct to provide attribute_groups
|
||||||
|
* customized for each extcon device.
|
||||||
|
* @cables: Sysfs subdirectories. Each represents one cable.
|
||||||
|
*
|
||||||
|
* In most cases, users only need to provide "User initializing data" of
|
||||||
|
* this struct when registering an extcon. In some exceptional cases,
|
||||||
|
* optional callbacks may be needed. However, the values in "internal data"
|
||||||
|
* are overwritten by register function.
|
||||||
|
*/
|
||||||
|
struct extcon_dev {
|
||||||
|
/* Optional user initializing data */
|
||||||
|
const char *name;
|
||||||
|
const unsigned int *supported_cable;
|
||||||
|
const u32 *mutually_exclusive;
|
||||||
|
|
||||||
|
/* Internal data. Please do not set. */
|
||||||
|
struct device dev;
|
||||||
|
struct raw_notifier_head *nh;
|
||||||
|
struct list_head entry;
|
||||||
|
int max_supported;
|
||||||
|
spinlock_t lock; /* could be called by irq handler */
|
||||||
|
u32 state;
|
||||||
|
|
||||||
|
/* /sys/class/extcon/.../cable.n/... */
|
||||||
|
struct device_type extcon_dev_type;
|
||||||
|
struct extcon_cable *cables;
|
||||||
|
|
||||||
|
/* /sys/class/extcon/.../mutually_exclusive/... */
|
||||||
|
struct attribute_group attr_g_muex;
|
||||||
|
struct attribute **attrs_muex;
|
||||||
|
struct device_attribute *d_attrs_muex;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* __LINUX_EXTCON_INTERNAL_H__ */
|
|
@ -25,16 +25,106 @@
|
||||||
#include <linux/of.h>
|
#include <linux/of.h>
|
||||||
#include <linux/mutex.h>
|
#include <linux/mutex.h>
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
|
#include <linux/scatterlist.h>
|
||||||
|
#include <linux/highmem.h>
|
||||||
|
|
||||||
static DEFINE_IDA(fpga_mgr_ida);
|
static DEFINE_IDA(fpga_mgr_ida);
|
||||||
static struct class *fpga_mgr_class;
|
static struct class *fpga_mgr_class;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Call the low level driver's write_init function. This will do the
|
||||||
|
* device-specific things to get the FPGA into the state where it is ready to
|
||||||
|
* receive an FPGA image. The low level driver only gets to see the first
|
||||||
|
* initial_header_size bytes in the buffer.
|
||||||
|
*/
|
||||||
|
static int fpga_mgr_write_init_buf(struct fpga_manager *mgr,
|
||||||
|
struct fpga_image_info *info,
|
||||||
|
const char *buf, size_t count)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
mgr->state = FPGA_MGR_STATE_WRITE_INIT;
|
||||||
|
if (!mgr->mops->initial_header_size)
|
||||||
|
ret = mgr->mops->write_init(mgr, info, NULL, 0);
|
||||||
|
else
|
||||||
|
ret = mgr->mops->write_init(
|
||||||
|
mgr, info, buf, min(mgr->mops->initial_header_size, count));
|
||||||
|
|
||||||
|
if (ret) {
|
||||||
|
dev_err(&mgr->dev, "Error preparing FPGA for writing\n");
|
||||||
|
mgr->state = FPGA_MGR_STATE_WRITE_INIT_ERR;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int fpga_mgr_write_init_sg(struct fpga_manager *mgr,
|
||||||
|
struct fpga_image_info *info,
|
||||||
|
struct sg_table *sgt)
|
||||||
|
{
|
||||||
|
struct sg_mapping_iter miter;
|
||||||
|
size_t len;
|
||||||
|
char *buf;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (!mgr->mops->initial_header_size)
|
||||||
|
return fpga_mgr_write_init_buf(mgr, info, NULL, 0);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* First try to use miter to map the first fragment to access the
|
||||||
|
* header, this is the typical path.
|
||||||
|
*/
|
||||||
|
sg_miter_start(&miter, sgt->sgl, sgt->nents, SG_MITER_FROM_SG);
|
||||||
|
if (sg_miter_next(&miter) &&
|
||||||
|
miter.length >= mgr->mops->initial_header_size) {
|
||||||
|
ret = fpga_mgr_write_init_buf(mgr, info, miter.addr,
|
||||||
|
miter.length);
|
||||||
|
sg_miter_stop(&miter);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
sg_miter_stop(&miter);
|
||||||
|
|
||||||
|
/* Otherwise copy the fragments into temporary memory. */
|
||||||
|
buf = kmalloc(mgr->mops->initial_header_size, GFP_KERNEL);
|
||||||
|
if (!buf)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
len = sg_copy_to_buffer(sgt->sgl, sgt->nents, buf,
|
||||||
|
mgr->mops->initial_header_size);
|
||||||
|
ret = fpga_mgr_write_init_buf(mgr, info, buf, len);
|
||||||
|
|
||||||
|
kfree(buf);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* After all the FPGA image has been written, do the device specific steps to
|
||||||
|
* finish and set the FPGA into operating mode.
|
||||||
|
*/
|
||||||
|
static int fpga_mgr_write_complete(struct fpga_manager *mgr,
|
||||||
|
struct fpga_image_info *info)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
mgr->state = FPGA_MGR_STATE_WRITE_COMPLETE;
|
||||||
|
ret = mgr->mops->write_complete(mgr, info);
|
||||||
|
if (ret) {
|
||||||
|
dev_err(&mgr->dev, "Error after writing image data to FPGA\n");
|
||||||
|
mgr->state = FPGA_MGR_STATE_WRITE_COMPLETE_ERR;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
mgr->state = FPGA_MGR_STATE_OPERATING;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* fpga_mgr_buf_load - load fpga from image in buffer
|
* fpga_mgr_buf_load_sg - load fpga from image in buffer from a scatter list
|
||||||
* @mgr: fpga manager
|
* @mgr: fpga manager
|
||||||
* @info: fpga image specific information
|
* @info: fpga image specific information
|
||||||
* @buf: buffer contain fpga image
|
* @sgt: scatterlist table
|
||||||
* @count: byte count of buf
|
|
||||||
*
|
*
|
||||||
* Step the low level fpga manager through the device-specific steps of getting
|
* Step the low level fpga manager through the device-specific steps of getting
|
||||||
* an FPGA ready to be configured, writing the image to it, then doing whatever
|
* an FPGA ready to be configured, writing the image to it, then doing whatever
|
||||||
|
@ -42,54 +132,139 @@ static struct class *fpga_mgr_class;
|
||||||
* mgr pointer from of_fpga_mgr_get() or fpga_mgr_get() and checked that it is
|
* mgr pointer from of_fpga_mgr_get() or fpga_mgr_get() and checked that it is
|
||||||
* not an error code.
|
* not an error code.
|
||||||
*
|
*
|
||||||
|
* This is the preferred entry point for FPGA programming, it does not require
|
||||||
|
* any contiguous kernel memory.
|
||||||
|
*
|
||||||
* Return: 0 on success, negative error code otherwise.
|
* Return: 0 on success, negative error code otherwise.
|
||||||
*/
|
*/
|
||||||
int fpga_mgr_buf_load(struct fpga_manager *mgr, struct fpga_image_info *info,
|
int fpga_mgr_buf_load_sg(struct fpga_manager *mgr, struct fpga_image_info *info,
|
||||||
const char *buf, size_t count)
|
struct sg_table *sgt)
|
||||||
{
|
{
|
||||||
struct device *dev = &mgr->dev;
|
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
/*
|
ret = fpga_mgr_write_init_sg(mgr, info, sgt);
|
||||||
* Call the low level driver's write_init function. This will do the
|
if (ret)
|
||||||
* device-specific things to get the FPGA into the state where it is
|
return ret;
|
||||||
* ready to receive an FPGA image. The low level driver only gets to
|
|
||||||
* see the first initial_header_size bytes in the buffer.
|
/* Write the FPGA image to the FPGA. */
|
||||||
*/
|
mgr->state = FPGA_MGR_STATE_WRITE;
|
||||||
mgr->state = FPGA_MGR_STATE_WRITE_INIT;
|
if (mgr->mops->write_sg) {
|
||||||
ret = mgr->mops->write_init(mgr, info, buf,
|
ret = mgr->mops->write_sg(mgr, sgt);
|
||||||
min(mgr->mops->initial_header_size, count));
|
} else {
|
||||||
|
struct sg_mapping_iter miter;
|
||||||
|
|
||||||
|
sg_miter_start(&miter, sgt->sgl, sgt->nents, SG_MITER_FROM_SG);
|
||||||
|
while (sg_miter_next(&miter)) {
|
||||||
|
ret = mgr->mops->write(mgr, miter.addr, miter.length);
|
||||||
|
if (ret)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
sg_miter_stop(&miter);
|
||||||
|
}
|
||||||
|
|
||||||
if (ret) {
|
if (ret) {
|
||||||
dev_err(dev, "Error preparing FPGA for writing\n");
|
dev_err(&mgr->dev, "Error while writing image data to FPGA\n");
|
||||||
mgr->state = FPGA_MGR_STATE_WRITE_INIT_ERR;
|
mgr->state = FPGA_MGR_STATE_WRITE_ERR;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return fpga_mgr_write_complete(mgr, info);
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(fpga_mgr_buf_load_sg);
|
||||||
|
|
||||||
|
static int fpga_mgr_buf_load_mapped(struct fpga_manager *mgr,
|
||||||
|
struct fpga_image_info *info,
|
||||||
|
const char *buf, size_t count)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = fpga_mgr_write_init_buf(mgr, info, buf, count);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Write the FPGA image to the FPGA.
|
* Write the FPGA image to the FPGA.
|
||||||
*/
|
*/
|
||||||
mgr->state = FPGA_MGR_STATE_WRITE;
|
mgr->state = FPGA_MGR_STATE_WRITE;
|
||||||
ret = mgr->mops->write(mgr, buf, count);
|
ret = mgr->mops->write(mgr, buf, count);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
dev_err(dev, "Error while writing image data to FPGA\n");
|
dev_err(&mgr->dev, "Error while writing image data to FPGA\n");
|
||||||
mgr->state = FPGA_MGR_STATE_WRITE_ERR;
|
mgr->state = FPGA_MGR_STATE_WRITE_ERR;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
return fpga_mgr_write_complete(mgr, info);
|
||||||
* After all the FPGA image has been written, do the device specific
|
|
||||||
* steps to finish and set the FPGA into operating mode.
|
|
||||||
*/
|
|
||||||
mgr->state = FPGA_MGR_STATE_WRITE_COMPLETE;
|
|
||||||
ret = mgr->mops->write_complete(mgr, info);
|
|
||||||
if (ret) {
|
|
||||||
dev_err(dev, "Error after writing image data to FPGA\n");
|
|
||||||
mgr->state = FPGA_MGR_STATE_WRITE_COMPLETE_ERR;
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
mgr->state = FPGA_MGR_STATE_OPERATING;
|
|
||||||
|
|
||||||
return 0;
|
/**
|
||||||
|
* fpga_mgr_buf_load - load fpga from image in buffer
|
||||||
|
* @mgr: fpga manager
|
||||||
|
* @flags: flags setting fpga confuration modes
|
||||||
|
* @buf: buffer contain fpga image
|
||||||
|
* @count: byte count of buf
|
||||||
|
*
|
||||||
|
* Step the low level fpga manager through the device-specific steps of getting
|
||||||
|
* an FPGA ready to be configured, writing the image to it, then doing whatever
|
||||||
|
* post-configuration steps necessary. This code assumes the caller got the
|
||||||
|
* mgr pointer from of_fpga_mgr_get() and checked that it is not an error code.
|
||||||
|
*
|
||||||
|
* Return: 0 on success, negative error code otherwise.
|
||||||
|
*/
|
||||||
|
int fpga_mgr_buf_load(struct fpga_manager *mgr, struct fpga_image_info *info,
|
||||||
|
const char *buf, size_t count)
|
||||||
|
{
|
||||||
|
struct page **pages;
|
||||||
|
struct sg_table sgt;
|
||||||
|
const void *p;
|
||||||
|
int nr_pages;
|
||||||
|
int index;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is just a fast path if the caller has already created a
|
||||||
|
* contiguous kernel buffer and the driver doesn't require SG, non-SG
|
||||||
|
* drivers will still work on the slow path.
|
||||||
|
*/
|
||||||
|
if (mgr->mops->write)
|
||||||
|
return fpga_mgr_buf_load_mapped(mgr, info, buf, count);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convert the linear kernel pointer into a sg_table of pages for use
|
||||||
|
* by the driver.
|
||||||
|
*/
|
||||||
|
nr_pages = DIV_ROUND_UP((unsigned long)buf + count, PAGE_SIZE) -
|
||||||
|
(unsigned long)buf / PAGE_SIZE;
|
||||||
|
pages = kmalloc_array(nr_pages, sizeof(struct page *), GFP_KERNEL);
|
||||||
|
if (!pages)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
p = buf - offset_in_page(buf);
|
||||||
|
for (index = 0; index < nr_pages; index++) {
|
||||||
|
if (is_vmalloc_addr(p))
|
||||||
|
pages[index] = vmalloc_to_page(p);
|
||||||
|
else
|
||||||
|
pages[index] = kmap_to_page((void *)p);
|
||||||
|
if (!pages[index]) {
|
||||||
|
kfree(pages);
|
||||||
|
return -EFAULT;
|
||||||
|
}
|
||||||
|
p += PAGE_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The temporary pages list is used to code share the merging algorithm
|
||||||
|
* in sg_alloc_table_from_pages
|
||||||
|
*/
|
||||||
|
rc = sg_alloc_table_from_pages(&sgt, pages, index, offset_in_page(buf),
|
||||||
|
count, GFP_KERNEL);
|
||||||
|
kfree(pages);
|
||||||
|
if (rc)
|
||||||
|
return rc;
|
||||||
|
|
||||||
|
rc = fpga_mgr_buf_load_sg(mgr, info, &sgt);
|
||||||
|
sg_free_table(&sgt);
|
||||||
|
|
||||||
|
return rc;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(fpga_mgr_buf_load);
|
EXPORT_SYMBOL_GPL(fpga_mgr_buf_load);
|
||||||
|
|
||||||
|
@ -291,8 +466,9 @@ int fpga_mgr_register(struct device *dev, const char *name,
|
||||||
struct fpga_manager *mgr;
|
struct fpga_manager *mgr;
|
||||||
int id, ret;
|
int id, ret;
|
||||||
|
|
||||||
if (!mops || !mops->write_init || !mops->write ||
|
if (!mops || !mops->write_complete || !mops->state ||
|
||||||
!mops->write_complete || !mops->state) {
|
!mops->write_init || (!mops->write && !mops->write_sg) ||
|
||||||
|
(mops->write && mops->write_sg)) {
|
||||||
dev_err(dev, "Attempt to register without fpga_manager_ops\n");
|
dev_err(dev, "Attempt to register without fpga_manager_ops\n");
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
#include <linux/pm.h>
|
#include <linux/pm.h>
|
||||||
#include <linux/regmap.h>
|
#include <linux/regmap.h>
|
||||||
#include <linux/string.h>
|
#include <linux/string.h>
|
||||||
|
#include <linux/scatterlist.h>
|
||||||
|
|
||||||
/* Offsets into SLCR regmap */
|
/* Offsets into SLCR regmap */
|
||||||
|
|
||||||
|
@ -80,6 +81,7 @@
|
||||||
|
|
||||||
/* FPGA init status */
|
/* FPGA init status */
|
||||||
#define STATUS_DMA_Q_F BIT(31)
|
#define STATUS_DMA_Q_F BIT(31)
|
||||||
|
#define STATUS_DMA_Q_E BIT(30)
|
||||||
#define STATUS_PCFG_INIT_MASK BIT(4)
|
#define STATUS_PCFG_INIT_MASK BIT(4)
|
||||||
|
|
||||||
/* Interrupt Status/Mask Register Bit definitions */
|
/* Interrupt Status/Mask Register Bit definitions */
|
||||||
|
@ -89,7 +91,7 @@
|
||||||
#define IXR_D_P_DONE_MASK BIT(12)
|
#define IXR_D_P_DONE_MASK BIT(12)
|
||||||
/* FPGA programmed */
|
/* FPGA programmed */
|
||||||
#define IXR_PCFG_DONE_MASK BIT(2)
|
#define IXR_PCFG_DONE_MASK BIT(2)
|
||||||
#define IXR_ERROR_FLAGS_MASK 0x00F0F860
|
#define IXR_ERROR_FLAGS_MASK 0x00F0C860
|
||||||
#define IXR_ALL_MASK 0xF8F7F87F
|
#define IXR_ALL_MASK 0xF8F7F87F
|
||||||
|
|
||||||
/* Miscellaneous constant values */
|
/* Miscellaneous constant values */
|
||||||
|
@ -98,12 +100,16 @@
|
||||||
#define DMA_INVALID_ADDRESS GENMASK(31, 0)
|
#define DMA_INVALID_ADDRESS GENMASK(31, 0)
|
||||||
/* Used to unlock the dev */
|
/* Used to unlock the dev */
|
||||||
#define UNLOCK_MASK 0x757bdf0d
|
#define UNLOCK_MASK 0x757bdf0d
|
||||||
/* Timeout for DMA to complete */
|
|
||||||
#define DMA_DONE_TIMEOUT msecs_to_jiffies(1000)
|
|
||||||
/* Timeout for polling reset bits */
|
/* Timeout for polling reset bits */
|
||||||
#define INIT_POLL_TIMEOUT 2500000
|
#define INIT_POLL_TIMEOUT 2500000
|
||||||
/* Delay for polling reset bits */
|
/* Delay for polling reset bits */
|
||||||
#define INIT_POLL_DELAY 20
|
#define INIT_POLL_DELAY 20
|
||||||
|
/* Signal this is the last DMA transfer, wait for the AXI and PCAP before
|
||||||
|
* interrupting
|
||||||
|
*/
|
||||||
|
#define DMA_SRC_LAST_TRANSFER 1
|
||||||
|
/* Timeout for DMA completion */
|
||||||
|
#define DMA_TIMEOUT_MS 5000
|
||||||
|
|
||||||
/* Masks for controlling stuff in SLCR */
|
/* Masks for controlling stuff in SLCR */
|
||||||
/* Disable all Level shifters */
|
/* Disable all Level shifters */
|
||||||
|
@ -124,6 +130,11 @@ struct zynq_fpga_priv {
|
||||||
void __iomem *io_base;
|
void __iomem *io_base;
|
||||||
struct regmap *slcr;
|
struct regmap *slcr;
|
||||||
|
|
||||||
|
spinlock_t dma_lock;
|
||||||
|
unsigned int dma_elm;
|
||||||
|
unsigned int dma_nelms;
|
||||||
|
struct scatterlist *cur_sg;
|
||||||
|
|
||||||
struct completion dma_done;
|
struct completion dma_done;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -143,37 +154,104 @@ static inline u32 zynq_fpga_read(const struct zynq_fpga_priv *priv,
|
||||||
readl_poll_timeout(priv->io_base + addr, val, cond, sleep_us, \
|
readl_poll_timeout(priv->io_base + addr, val, cond, sleep_us, \
|
||||||
timeout_us)
|
timeout_us)
|
||||||
|
|
||||||
static void zynq_fpga_mask_irqs(struct zynq_fpga_priv *priv)
|
/* Cause the specified irq mask bits to generate IRQs */
|
||||||
|
static inline void zynq_fpga_set_irq(struct zynq_fpga_priv *priv, u32 enable)
|
||||||
{
|
{
|
||||||
u32 intr_mask;
|
zynq_fpga_write(priv, INT_MASK_OFFSET, ~enable);
|
||||||
|
|
||||||
intr_mask = zynq_fpga_read(priv, INT_MASK_OFFSET);
|
|
||||||
zynq_fpga_write(priv, INT_MASK_OFFSET,
|
|
||||||
intr_mask | IXR_DMA_DONE_MASK | IXR_ERROR_FLAGS_MASK);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void zynq_fpga_unmask_irqs(struct zynq_fpga_priv *priv)
|
/* Must be called with dma_lock held */
|
||||||
|
static void zynq_step_dma(struct zynq_fpga_priv *priv)
|
||||||
{
|
{
|
||||||
u32 intr_mask;
|
u32 addr;
|
||||||
|
u32 len;
|
||||||
|
bool first;
|
||||||
|
|
||||||
intr_mask = zynq_fpga_read(priv, INT_MASK_OFFSET);
|
first = priv->dma_elm == 0;
|
||||||
zynq_fpga_write(priv, INT_MASK_OFFSET,
|
while (priv->cur_sg) {
|
||||||
intr_mask
|
/* Feed the DMA queue until it is full. */
|
||||||
& ~(IXR_D_P_DONE_MASK | IXR_ERROR_FLAGS_MASK));
|
if (zynq_fpga_read(priv, STATUS_OFFSET) & STATUS_DMA_Q_F)
|
||||||
|
break;
|
||||||
|
|
||||||
|
addr = sg_dma_address(priv->cur_sg);
|
||||||
|
len = sg_dma_len(priv->cur_sg);
|
||||||
|
if (priv->dma_elm + 1 == priv->dma_nelms) {
|
||||||
|
/* The last transfer waits for the PCAP to finish too,
|
||||||
|
* notice this also changes the irq_mask to ignore
|
||||||
|
* IXR_DMA_DONE_MASK which ensures we do not trigger
|
||||||
|
* the completion too early.
|
||||||
|
*/
|
||||||
|
addr |= DMA_SRC_LAST_TRANSFER;
|
||||||
|
priv->cur_sg = NULL;
|
||||||
|
} else {
|
||||||
|
priv->cur_sg = sg_next(priv->cur_sg);
|
||||||
|
priv->dma_elm++;
|
||||||
|
}
|
||||||
|
|
||||||
|
zynq_fpga_write(priv, DMA_SRC_ADDR_OFFSET, addr);
|
||||||
|
zynq_fpga_write(priv, DMA_DST_ADDR_OFFSET, DMA_INVALID_ADDRESS);
|
||||||
|
zynq_fpga_write(priv, DMA_SRC_LEN_OFFSET, len / 4);
|
||||||
|
zynq_fpga_write(priv, DMA_DEST_LEN_OFFSET, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Once the first transfer is queued we can turn on the ISR, future
|
||||||
|
* calls to zynq_step_dma will happen from the ISR context. The
|
||||||
|
* dma_lock spinlock guarentees this handover is done coherently, the
|
||||||
|
* ISR enable is put at the end to avoid another CPU spinning in the
|
||||||
|
* ISR on this lock.
|
||||||
|
*/
|
||||||
|
if (first && priv->cur_sg) {
|
||||||
|
zynq_fpga_set_irq(priv,
|
||||||
|
IXR_DMA_DONE_MASK | IXR_ERROR_FLAGS_MASK);
|
||||||
|
} else if (!priv->cur_sg) {
|
||||||
|
/* The last transfer changes to DMA & PCAP mode since we do
|
||||||
|
* not want to continue until everything has been flushed into
|
||||||
|
* the PCAP.
|
||||||
|
*/
|
||||||
|
zynq_fpga_set_irq(priv,
|
||||||
|
IXR_D_P_DONE_MASK | IXR_ERROR_FLAGS_MASK);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static irqreturn_t zynq_fpga_isr(int irq, void *data)
|
static irqreturn_t zynq_fpga_isr(int irq, void *data)
|
||||||
{
|
{
|
||||||
struct zynq_fpga_priv *priv = data;
|
struct zynq_fpga_priv *priv = data;
|
||||||
|
u32 intr_status;
|
||||||
|
|
||||||
/* disable DMA and error IRQs */
|
/* If anything other than DMA completion is reported stop and hand
|
||||||
zynq_fpga_mask_irqs(priv);
|
* control back to zynq_fpga_ops_write, something went wrong,
|
||||||
|
* otherwise progress the DMA.
|
||||||
|
*/
|
||||||
|
spin_lock(&priv->dma_lock);
|
||||||
|
intr_status = zynq_fpga_read(priv, INT_STS_OFFSET);
|
||||||
|
if (!(intr_status & IXR_ERROR_FLAGS_MASK) &&
|
||||||
|
(intr_status & IXR_DMA_DONE_MASK) && priv->cur_sg) {
|
||||||
|
zynq_fpga_write(priv, INT_STS_OFFSET, IXR_DMA_DONE_MASK);
|
||||||
|
zynq_step_dma(priv);
|
||||||
|
spin_unlock(&priv->dma_lock);
|
||||||
|
return IRQ_HANDLED;
|
||||||
|
}
|
||||||
|
spin_unlock(&priv->dma_lock);
|
||||||
|
|
||||||
|
zynq_fpga_set_irq(priv, 0);
|
||||||
complete(&priv->dma_done);
|
complete(&priv->dma_done);
|
||||||
|
|
||||||
return IRQ_HANDLED;
|
return IRQ_HANDLED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Sanity check the proposed bitstream. It must start with the sync word in
|
||||||
|
* the correct byte order, and be dword aligned. The input is a Xilinx .bin
|
||||||
|
* file with every 32 bit quantity swapped.
|
||||||
|
*/
|
||||||
|
static bool zynq_fpga_has_sync(const u8 *buf, size_t count)
|
||||||
|
{
|
||||||
|
for (; count >= 4; buf += 4, count -= 4)
|
||||||
|
if (buf[0] == 0x66 && buf[1] == 0x55 && buf[2] == 0x99 &&
|
||||||
|
buf[3] == 0xaa)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
static int zynq_fpga_ops_write_init(struct fpga_manager *mgr,
|
static int zynq_fpga_ops_write_init(struct fpga_manager *mgr,
|
||||||
struct fpga_image_info *info,
|
struct fpga_image_info *info,
|
||||||
const char *buf, size_t count)
|
const char *buf, size_t count)
|
||||||
|
@ -190,6 +268,13 @@ static int zynq_fpga_ops_write_init(struct fpga_manager *mgr,
|
||||||
|
|
||||||
/* don't globally reset PL if we're doing partial reconfig */
|
/* don't globally reset PL if we're doing partial reconfig */
|
||||||
if (!(info->flags & FPGA_MGR_PARTIAL_RECONFIG)) {
|
if (!(info->flags & FPGA_MGR_PARTIAL_RECONFIG)) {
|
||||||
|
if (!zynq_fpga_has_sync(buf, count)) {
|
||||||
|
dev_err(&mgr->dev,
|
||||||
|
"Invalid bitstream, could not find a sync word. Bitstream must be a byte swapped .bin file\n");
|
||||||
|
err = -EINVAL;
|
||||||
|
goto out_err;
|
||||||
|
}
|
||||||
|
|
||||||
/* assert AXI interface resets */
|
/* assert AXI interface resets */
|
||||||
regmap_write(priv->slcr, SLCR_FPGA_RST_CTRL_OFFSET,
|
regmap_write(priv->slcr, SLCR_FPGA_RST_CTRL_OFFSET,
|
||||||
FPGA_RST_ALL_MASK);
|
FPGA_RST_ALL_MASK);
|
||||||
|
@ -259,10 +344,11 @@ static int zynq_fpga_ops_write_init(struct fpga_manager *mgr,
|
||||||
zynq_fpga_write(priv, CTRL_OFFSET,
|
zynq_fpga_write(priv, CTRL_OFFSET,
|
||||||
(CTRL_PCAP_PR_MASK | CTRL_PCAP_MODE_MASK | ctrl));
|
(CTRL_PCAP_PR_MASK | CTRL_PCAP_MODE_MASK | ctrl));
|
||||||
|
|
||||||
/* check that we have room in the command queue */
|
/* We expect that the command queue is empty right now. */
|
||||||
status = zynq_fpga_read(priv, STATUS_OFFSET);
|
status = zynq_fpga_read(priv, STATUS_OFFSET);
|
||||||
if (status & STATUS_DMA_Q_F) {
|
if ((status & STATUS_DMA_Q_F) ||
|
||||||
dev_err(&mgr->dev, "DMA command queue full\n");
|
(status & STATUS_DMA_Q_E) != STATUS_DMA_Q_E) {
|
||||||
|
dev_err(&mgr->dev, "DMA command queue not right\n");
|
||||||
err = -EBUSY;
|
err = -EBUSY;
|
||||||
goto out_err;
|
goto out_err;
|
||||||
}
|
}
|
||||||
|
@ -281,26 +367,36 @@ static int zynq_fpga_ops_write_init(struct fpga_manager *mgr,
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int zynq_fpga_ops_write(struct fpga_manager *mgr,
|
static int zynq_fpga_ops_write(struct fpga_manager *mgr, struct sg_table *sgt)
|
||||||
const char *buf, size_t count)
|
|
||||||
{
|
{
|
||||||
struct zynq_fpga_priv *priv;
|
struct zynq_fpga_priv *priv;
|
||||||
|
const char *why;
|
||||||
int err;
|
int err;
|
||||||
char *kbuf;
|
|
||||||
size_t in_count;
|
|
||||||
dma_addr_t dma_addr;
|
|
||||||
u32 transfer_length;
|
|
||||||
u32 intr_status;
|
u32 intr_status;
|
||||||
|
unsigned long timeout;
|
||||||
|
unsigned long flags;
|
||||||
|
struct scatterlist *sg;
|
||||||
|
int i;
|
||||||
|
|
||||||
in_count = count;
|
|
||||||
priv = mgr->priv;
|
priv = mgr->priv;
|
||||||
|
|
||||||
kbuf =
|
/* The hardware can only DMA multiples of 4 bytes, and it requires the
|
||||||
dma_alloc_coherent(mgr->dev.parent, count, &dma_addr, GFP_KERNEL);
|
* starting addresses to be aligned to 64 bits (UG585 pg 212).
|
||||||
if (!kbuf)
|
*/
|
||||||
return -ENOMEM;
|
for_each_sg(sgt->sgl, sg, sgt->nents, i) {
|
||||||
|
if ((sg->offset % 8) || (sg->length % 4)) {
|
||||||
|
dev_err(&mgr->dev,
|
||||||
|
"Invalid bitstream, chunks must be aligned\n");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
memcpy(kbuf, buf, count);
|
priv->dma_nelms =
|
||||||
|
dma_map_sg(mgr->dev.parent, sgt->sgl, sgt->nents, DMA_TO_DEVICE);
|
||||||
|
if (priv->dma_nelms == 0) {
|
||||||
|
dev_err(&mgr->dev, "Unable to DMA map (TO_DEVICE)\n");
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
/* enable clock */
|
/* enable clock */
|
||||||
err = clk_enable(priv->clk);
|
err = clk_enable(priv->clk);
|
||||||
|
@ -308,38 +404,67 @@ static int zynq_fpga_ops_write(struct fpga_manager *mgr,
|
||||||
goto out_free;
|
goto out_free;
|
||||||
|
|
||||||
zynq_fpga_write(priv, INT_STS_OFFSET, IXR_ALL_MASK);
|
zynq_fpga_write(priv, INT_STS_OFFSET, IXR_ALL_MASK);
|
||||||
|
|
||||||
reinit_completion(&priv->dma_done);
|
reinit_completion(&priv->dma_done);
|
||||||
|
|
||||||
/* enable DMA and error IRQs */
|
/* zynq_step_dma will turn on interrupts */
|
||||||
zynq_fpga_unmask_irqs(priv);
|
spin_lock_irqsave(&priv->dma_lock, flags);
|
||||||
|
priv->dma_elm = 0;
|
||||||
|
priv->cur_sg = sgt->sgl;
|
||||||
|
zynq_step_dma(priv);
|
||||||
|
spin_unlock_irqrestore(&priv->dma_lock, flags);
|
||||||
|
|
||||||
/* the +1 in the src addr is used to hold off on DMA_DONE IRQ
|
timeout = wait_for_completion_timeout(&priv->dma_done,
|
||||||
* until both AXI and PCAP are done ...
|
msecs_to_jiffies(DMA_TIMEOUT_MS));
|
||||||
*/
|
|
||||||
zynq_fpga_write(priv, DMA_SRC_ADDR_OFFSET, (u32)(dma_addr) + 1);
|
|
||||||
zynq_fpga_write(priv, DMA_DST_ADDR_OFFSET, (u32)DMA_INVALID_ADDRESS);
|
|
||||||
|
|
||||||
/* convert #bytes to #words */
|
spin_lock_irqsave(&priv->dma_lock, flags);
|
||||||
transfer_length = (count + 3) / 4;
|
zynq_fpga_set_irq(priv, 0);
|
||||||
|
priv->cur_sg = NULL;
|
||||||
zynq_fpga_write(priv, DMA_SRC_LEN_OFFSET, transfer_length);
|
spin_unlock_irqrestore(&priv->dma_lock, flags);
|
||||||
zynq_fpga_write(priv, DMA_DEST_LEN_OFFSET, 0);
|
|
||||||
|
|
||||||
wait_for_completion(&priv->dma_done);
|
|
||||||
|
|
||||||
intr_status = zynq_fpga_read(priv, INT_STS_OFFSET);
|
intr_status = zynq_fpga_read(priv, INT_STS_OFFSET);
|
||||||
zynq_fpga_write(priv, INT_STS_OFFSET, intr_status);
|
zynq_fpga_write(priv, INT_STS_OFFSET, IXR_ALL_MASK);
|
||||||
|
|
||||||
if (!((intr_status & IXR_D_P_DONE_MASK) == IXR_D_P_DONE_MASK)) {
|
/* There doesn't seem to be a way to force cancel any DMA, so if
|
||||||
dev_err(&mgr->dev, "Error configuring FPGA\n");
|
* something went wrong we are relying on the hardware to have halted
|
||||||
err = -EFAULT;
|
* the DMA before we get here, if there was we could use
|
||||||
|
* wait_for_completion_interruptible too.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (intr_status & IXR_ERROR_FLAGS_MASK) {
|
||||||
|
why = "DMA reported error";
|
||||||
|
err = -EIO;
|
||||||
|
goto out_report;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (priv->cur_sg ||
|
||||||
|
!((intr_status & IXR_D_P_DONE_MASK) == IXR_D_P_DONE_MASK)) {
|
||||||
|
if (timeout == 0)
|
||||||
|
why = "DMA timed out";
|
||||||
|
else
|
||||||
|
why = "DMA did not complete";
|
||||||
|
err = -EIO;
|
||||||
|
goto out_report;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = 0;
|
||||||
|
goto out_clk;
|
||||||
|
|
||||||
|
out_report:
|
||||||
|
dev_err(&mgr->dev,
|
||||||
|
"%s: INT_STS:0x%x CTRL:0x%x LOCK:0x%x INT_MASK:0x%x STATUS:0x%x MCTRL:0x%x\n",
|
||||||
|
why,
|
||||||
|
intr_status,
|
||||||
|
zynq_fpga_read(priv, CTRL_OFFSET),
|
||||||
|
zynq_fpga_read(priv, LOCK_OFFSET),
|
||||||
|
zynq_fpga_read(priv, INT_MASK_OFFSET),
|
||||||
|
zynq_fpga_read(priv, STATUS_OFFSET),
|
||||||
|
zynq_fpga_read(priv, MCTRL_OFFSET));
|
||||||
|
|
||||||
|
out_clk:
|
||||||
clk_disable(priv->clk);
|
clk_disable(priv->clk);
|
||||||
|
|
||||||
out_free:
|
out_free:
|
||||||
dma_free_coherent(mgr->dev.parent, count, kbuf, dma_addr);
|
dma_unmap_sg(mgr->dev.parent, sgt->sgl, sgt->nents, DMA_TO_DEVICE);
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -400,9 +525,10 @@ static enum fpga_mgr_states zynq_fpga_ops_state(struct fpga_manager *mgr)
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct fpga_manager_ops zynq_fpga_ops = {
|
static const struct fpga_manager_ops zynq_fpga_ops = {
|
||||||
|
.initial_header_size = 128,
|
||||||
.state = zynq_fpga_ops_state,
|
.state = zynq_fpga_ops_state,
|
||||||
.write_init = zynq_fpga_ops_write_init,
|
.write_init = zynq_fpga_ops_write_init,
|
||||||
.write = zynq_fpga_ops_write,
|
.write_sg = zynq_fpga_ops_write,
|
||||||
.write_complete = zynq_fpga_ops_write_complete,
|
.write_complete = zynq_fpga_ops_write_complete,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -416,6 +542,7 @@ static int zynq_fpga_probe(struct platform_device *pdev)
|
||||||
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
||||||
if (!priv)
|
if (!priv)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
spin_lock_init(&priv->dma_lock);
|
||||||
|
|
||||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||||
priv->io_base = devm_ioremap_resource(dev, res);
|
priv->io_base = devm_ioremap_resource(dev, res);
|
||||||
|
@ -452,7 +579,7 @@ static int zynq_fpga_probe(struct platform_device *pdev)
|
||||||
/* unlock the device */
|
/* unlock the device */
|
||||||
zynq_fpga_write(priv, UNLOCK_OFFSET, UNLOCK_MASK);
|
zynq_fpga_write(priv, UNLOCK_OFFSET, UNLOCK_MASK);
|
||||||
|
|
||||||
zynq_fpga_write(priv, INT_MASK_OFFSET, 0xFFFFFFFF);
|
zynq_fpga_set_irq(priv, 0);
|
||||||
zynq_fpga_write(priv, INT_STS_OFFSET, IXR_ALL_MASK);
|
zynq_fpga_write(priv, INT_STS_OFFSET, IXR_ALL_MASK);
|
||||||
err = devm_request_irq(dev, priv->irq, zynq_fpga_isr, 0, dev_name(dev),
|
err = devm_request_irq(dev, priv->irq, zynq_fpga_isr, 0, dev_name(dev),
|
||||||
priv);
|
priv);
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
#
|
||||||
|
# FSI subsystem
|
||||||
|
#
|
||||||
|
|
||||||
|
menu "FSI support"
|
||||||
|
|
||||||
|
config FSI
|
||||||
|
tristate "FSI support"
|
||||||
|
---help---
|
||||||
|
FSI - the FRU Support Interface - is a simple bus for low-level
|
||||||
|
access to POWER-based hardware.
|
||||||
|
endmenu
|
|
@ -0,0 +1,2 @@
|
||||||
|
|
||||||
|
obj-$(CONFIG_FSI) += fsi-core.o
|
|
@ -0,0 +1,59 @@
|
||||||
|
/*
|
||||||
|
* FSI core driver
|
||||||
|
*
|
||||||
|
* Copyright (C) IBM Corporation 2016
|
||||||
|
*
|
||||||
|
* 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/device.h>
|
||||||
|
#include <linux/fsi.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
|
||||||
|
/* FSI core & Linux bus type definitions */
|
||||||
|
|
||||||
|
static int fsi_bus_match(struct device *dev, struct device_driver *drv)
|
||||||
|
{
|
||||||
|
struct fsi_device *fsi_dev = to_fsi_dev(dev);
|
||||||
|
struct fsi_driver *fsi_drv = to_fsi_drv(drv);
|
||||||
|
const struct fsi_device_id *id;
|
||||||
|
|
||||||
|
if (!fsi_drv->id_table)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
for (id = fsi_drv->id_table; id->engine_type; id++) {
|
||||||
|
if (id->engine_type != fsi_dev->engine_type)
|
||||||
|
continue;
|
||||||
|
if (id->version == FSI_VERSION_ANY ||
|
||||||
|
id->version == fsi_dev->version)
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct bus_type fsi_bus_type = {
|
||||||
|
.name = "fsi",
|
||||||
|
.match = fsi_bus_match,
|
||||||
|
};
|
||||||
|
EXPORT_SYMBOL_GPL(fsi_bus_type);
|
||||||
|
|
||||||
|
static int fsi_init(void)
|
||||||
|
{
|
||||||
|
return bus_register(&fsi_bus_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fsi_exit(void)
|
||||||
|
{
|
||||||
|
bus_unregister(&fsi_bus_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
module_init(fsi_init);
|
||||||
|
module_exit(fsi_exit);
|
|
@ -47,12 +47,8 @@ void vmbus_setevent(struct vmbus_channel *channel)
|
||||||
* For channels marked as in "low latency" mode
|
* For channels marked as in "low latency" mode
|
||||||
* bypass the monitor page mechanism.
|
* bypass the monitor page mechanism.
|
||||||
*/
|
*/
|
||||||
if ((channel->offermsg.monitor_allocated) &&
|
if (channel->offermsg.monitor_allocated && !channel->low_latency) {
|
||||||
(!channel->low_latency)) {
|
vmbus_send_interrupt(channel->offermsg.child_relid);
|
||||||
/* Each u32 represents 32 channels */
|
|
||||||
sync_set_bit(channel->offermsg.child_relid & 31,
|
|
||||||
(unsigned long *) vmbus_connection.send_int_page +
|
|
||||||
(channel->offermsg.child_relid >> 5));
|
|
||||||
|
|
||||||
/* Get the child to parent monitor page */
|
/* Get the child to parent monitor page */
|
||||||
monitorpage = vmbus_connection.monitor_pages[1];
|
monitorpage = vmbus_connection.monitor_pages[1];
|
||||||
|
@ -157,6 +153,7 @@ int vmbus_open(struct vmbus_channel *newchannel, u32 send_ringbuffer_size,
|
||||||
}
|
}
|
||||||
|
|
||||||
init_completion(&open_info->waitevent);
|
init_completion(&open_info->waitevent);
|
||||||
|
open_info->waiting_channel = newchannel;
|
||||||
|
|
||||||
open_msg = (struct vmbus_channel_open_channel *)open_info->msg;
|
open_msg = (struct vmbus_channel_open_channel *)open_info->msg;
|
||||||
open_msg->header.msgtype = CHANNELMSG_OPENCHANNEL;
|
open_msg->header.msgtype = CHANNELMSG_OPENCHANNEL;
|
||||||
|
@ -181,7 +178,7 @@ int vmbus_open(struct vmbus_channel *newchannel, u32 send_ringbuffer_size,
|
||||||
spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock, flags);
|
spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock, flags);
|
||||||
|
|
||||||
ret = vmbus_post_msg(open_msg,
|
ret = vmbus_post_msg(open_msg,
|
||||||
sizeof(struct vmbus_channel_open_channel));
|
sizeof(struct vmbus_channel_open_channel), true);
|
||||||
|
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
err = ret;
|
err = ret;
|
||||||
|
@ -194,6 +191,11 @@ int vmbus_open(struct vmbus_channel *newchannel, u32 send_ringbuffer_size,
|
||||||
list_del(&open_info->msglistentry);
|
list_del(&open_info->msglistentry);
|
||||||
spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock, flags);
|
spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock, flags);
|
||||||
|
|
||||||
|
if (newchannel->rescind) {
|
||||||
|
err = -ENODEV;
|
||||||
|
goto error_free_gpadl;
|
||||||
|
}
|
||||||
|
|
||||||
if (open_info->response.open_result.status) {
|
if (open_info->response.open_result.status) {
|
||||||
err = -EAGAIN;
|
err = -EAGAIN;
|
||||||
goto error_free_gpadl;
|
goto error_free_gpadl;
|
||||||
|
@ -233,7 +235,7 @@ int vmbus_send_tl_connect_request(const uuid_le *shv_guest_servie_id,
|
||||||
conn_msg.guest_endpoint_id = *shv_guest_servie_id;
|
conn_msg.guest_endpoint_id = *shv_guest_servie_id;
|
||||||
conn_msg.host_service_id = *shv_host_servie_id;
|
conn_msg.host_service_id = *shv_host_servie_id;
|
||||||
|
|
||||||
return vmbus_post_msg(&conn_msg, sizeof(conn_msg));
|
return vmbus_post_msg(&conn_msg, sizeof(conn_msg), true);
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(vmbus_send_tl_connect_request);
|
EXPORT_SYMBOL_GPL(vmbus_send_tl_connect_request);
|
||||||
|
|
||||||
|
@ -405,6 +407,7 @@ int vmbus_establish_gpadl(struct vmbus_channel *channel, void *kbuffer,
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
init_completion(&msginfo->waitevent);
|
init_completion(&msginfo->waitevent);
|
||||||
|
msginfo->waiting_channel = channel;
|
||||||
|
|
||||||
gpadlmsg = (struct vmbus_channel_gpadl_header *)msginfo->msg;
|
gpadlmsg = (struct vmbus_channel_gpadl_header *)msginfo->msg;
|
||||||
gpadlmsg->header.msgtype = CHANNELMSG_GPADL_HEADER;
|
gpadlmsg->header.msgtype = CHANNELMSG_GPADL_HEADER;
|
||||||
|
@ -419,7 +422,7 @@ int vmbus_establish_gpadl(struct vmbus_channel *channel, void *kbuffer,
|
||||||
spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock, flags);
|
spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock, flags);
|
||||||
|
|
||||||
ret = vmbus_post_msg(gpadlmsg, msginfo->msgsize -
|
ret = vmbus_post_msg(gpadlmsg, msginfo->msgsize -
|
||||||
sizeof(*msginfo));
|
sizeof(*msginfo), true);
|
||||||
if (ret != 0)
|
if (ret != 0)
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
|
|
||||||
|
@ -433,14 +436,19 @@ int vmbus_establish_gpadl(struct vmbus_channel *channel, void *kbuffer,
|
||||||
gpadl_body->gpadl = next_gpadl_handle;
|
gpadl_body->gpadl = next_gpadl_handle;
|
||||||
|
|
||||||
ret = vmbus_post_msg(gpadl_body,
|
ret = vmbus_post_msg(gpadl_body,
|
||||||
submsginfo->msgsize -
|
submsginfo->msgsize - sizeof(*submsginfo),
|
||||||
sizeof(*submsginfo));
|
true);
|
||||||
if (ret != 0)
|
if (ret != 0)
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
|
|
||||||
}
|
}
|
||||||
wait_for_completion(&msginfo->waitevent);
|
wait_for_completion(&msginfo->waitevent);
|
||||||
|
|
||||||
|
if (channel->rescind) {
|
||||||
|
ret = -ENODEV;
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
/* At this point, we received the gpadl created msg */
|
/* At this point, we received the gpadl created msg */
|
||||||
*gpadl_handle = gpadlmsg->gpadl;
|
*gpadl_handle = gpadlmsg->gpadl;
|
||||||
|
|
||||||
|
@ -474,6 +482,7 @@ int vmbus_teardown_gpadl(struct vmbus_channel *channel, u32 gpadl_handle)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
|
||||||
init_completion(&info->waitevent);
|
init_completion(&info->waitevent);
|
||||||
|
info->waiting_channel = channel;
|
||||||
|
|
||||||
msg = (struct vmbus_channel_gpadl_teardown *)info->msg;
|
msg = (struct vmbus_channel_gpadl_teardown *)info->msg;
|
||||||
|
|
||||||
|
@ -485,14 +494,19 @@ int vmbus_teardown_gpadl(struct vmbus_channel *channel, u32 gpadl_handle)
|
||||||
list_add_tail(&info->msglistentry,
|
list_add_tail(&info->msglistentry,
|
||||||
&vmbus_connection.chn_msg_list);
|
&vmbus_connection.chn_msg_list);
|
||||||
spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock, flags);
|
spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock, flags);
|
||||||
ret = vmbus_post_msg(msg,
|
ret = vmbus_post_msg(msg, sizeof(struct vmbus_channel_gpadl_teardown),
|
||||||
sizeof(struct vmbus_channel_gpadl_teardown));
|
true);
|
||||||
|
|
||||||
if (ret)
|
if (ret)
|
||||||
goto post_msg_err;
|
goto post_msg_err;
|
||||||
|
|
||||||
wait_for_completion(&info->waitevent);
|
wait_for_completion(&info->waitevent);
|
||||||
|
|
||||||
|
if (channel->rescind) {
|
||||||
|
ret = -ENODEV;
|
||||||
|
goto post_msg_err;
|
||||||
|
}
|
||||||
|
|
||||||
post_msg_err:
|
post_msg_err:
|
||||||
spin_lock_irqsave(&vmbus_connection.channelmsg_lock, flags);
|
spin_lock_irqsave(&vmbus_connection.channelmsg_lock, flags);
|
||||||
list_del(&info->msglistentry);
|
list_del(&info->msglistentry);
|
||||||
|
@ -516,7 +530,7 @@ static int vmbus_close_internal(struct vmbus_channel *channel)
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* process_chn_event(), running in the tasklet, can race
|
* vmbus_on_event(), running in the tasklet, can race
|
||||||
* with vmbus_close_internal() in the case of SMP guest, e.g., when
|
* with vmbus_close_internal() in the case of SMP guest, e.g., when
|
||||||
* the former is accessing channel->inbound.ring_buffer, the latter
|
* the former is accessing channel->inbound.ring_buffer, the latter
|
||||||
* could be freeing the ring_buffer pages.
|
* could be freeing the ring_buffer pages.
|
||||||
|
@ -557,7 +571,8 @@ static int vmbus_close_internal(struct vmbus_channel *channel)
|
||||||
msg->header.msgtype = CHANNELMSG_CLOSECHANNEL;
|
msg->header.msgtype = CHANNELMSG_CLOSECHANNEL;
|
||||||
msg->child_relid = channel->offermsg.child_relid;
|
msg->child_relid = channel->offermsg.child_relid;
|
||||||
|
|
||||||
ret = vmbus_post_msg(msg, sizeof(struct vmbus_channel_close_channel));
|
ret = vmbus_post_msg(msg, sizeof(struct vmbus_channel_close_channel),
|
||||||
|
true);
|
||||||
|
|
||||||
if (ret) {
|
if (ret) {
|
||||||
pr_err("Close failed: close post msg return is %d\n", ret);
|
pr_err("Close failed: close post msg return is %d\n", ret);
|
||||||
|
@ -629,14 +644,13 @@ EXPORT_SYMBOL_GPL(vmbus_close);
|
||||||
|
|
||||||
int vmbus_sendpacket_ctl(struct vmbus_channel *channel, void *buffer,
|
int vmbus_sendpacket_ctl(struct vmbus_channel *channel, void *buffer,
|
||||||
u32 bufferlen, u64 requestid,
|
u32 bufferlen, u64 requestid,
|
||||||
enum vmbus_packet_type type, u32 flags, bool kick_q)
|
enum vmbus_packet_type type, u32 flags)
|
||||||
{
|
{
|
||||||
struct vmpacket_descriptor desc;
|
struct vmpacket_descriptor desc;
|
||||||
u32 packetlen = sizeof(struct vmpacket_descriptor) + bufferlen;
|
u32 packetlen = sizeof(struct vmpacket_descriptor) + bufferlen;
|
||||||
u32 packetlen_aligned = ALIGN(packetlen, sizeof(u64));
|
u32 packetlen_aligned = ALIGN(packetlen, sizeof(u64));
|
||||||
struct kvec bufferlist[3];
|
struct kvec bufferlist[3];
|
||||||
u64 aligned_data = 0;
|
u64 aligned_data = 0;
|
||||||
bool lock = channel->acquire_ring_lock;
|
|
||||||
int num_vecs = ((bufferlen != 0) ? 3 : 1);
|
int num_vecs = ((bufferlen != 0) ? 3 : 1);
|
||||||
|
|
||||||
|
|
||||||
|
@ -655,9 +669,7 @@ int vmbus_sendpacket_ctl(struct vmbus_channel *channel, void *buffer,
|
||||||
bufferlist[2].iov_base = &aligned_data;
|
bufferlist[2].iov_base = &aligned_data;
|
||||||
bufferlist[2].iov_len = (packetlen_aligned - packetlen);
|
bufferlist[2].iov_len = (packetlen_aligned - packetlen);
|
||||||
|
|
||||||
return hv_ringbuffer_write(channel, bufferlist, num_vecs,
|
return hv_ringbuffer_write(channel, bufferlist, num_vecs);
|
||||||
lock, kick_q);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(vmbus_sendpacket_ctl);
|
EXPORT_SYMBOL(vmbus_sendpacket_ctl);
|
||||||
|
|
||||||
|
@ -680,7 +692,7 @@ int vmbus_sendpacket(struct vmbus_channel *channel, void *buffer,
|
||||||
enum vmbus_packet_type type, u32 flags)
|
enum vmbus_packet_type type, u32 flags)
|
||||||
{
|
{
|
||||||
return vmbus_sendpacket_ctl(channel, buffer, bufferlen, requestid,
|
return vmbus_sendpacket_ctl(channel, buffer, bufferlen, requestid,
|
||||||
type, flags, true);
|
type, flags);
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(vmbus_sendpacket);
|
EXPORT_SYMBOL(vmbus_sendpacket);
|
||||||
|
|
||||||
|
@ -694,9 +706,7 @@ EXPORT_SYMBOL(vmbus_sendpacket);
|
||||||
int vmbus_sendpacket_pagebuffer_ctl(struct vmbus_channel *channel,
|
int vmbus_sendpacket_pagebuffer_ctl(struct vmbus_channel *channel,
|
||||||
struct hv_page_buffer pagebuffers[],
|
struct hv_page_buffer pagebuffers[],
|
||||||
u32 pagecount, void *buffer, u32 bufferlen,
|
u32 pagecount, void *buffer, u32 bufferlen,
|
||||||
u64 requestid,
|
u64 requestid, u32 flags)
|
||||||
u32 flags,
|
|
||||||
bool kick_q)
|
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
struct vmbus_channel_packet_page_buffer desc;
|
struct vmbus_channel_packet_page_buffer desc;
|
||||||
|
@ -705,12 +715,10 @@ int vmbus_sendpacket_pagebuffer_ctl(struct vmbus_channel *channel,
|
||||||
u32 packetlen_aligned;
|
u32 packetlen_aligned;
|
||||||
struct kvec bufferlist[3];
|
struct kvec bufferlist[3];
|
||||||
u64 aligned_data = 0;
|
u64 aligned_data = 0;
|
||||||
bool lock = channel->acquire_ring_lock;
|
|
||||||
|
|
||||||
if (pagecount > MAX_PAGE_BUFFER_COUNT)
|
if (pagecount > MAX_PAGE_BUFFER_COUNT)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Adjust the size down since vmbus_channel_packet_page_buffer is the
|
* Adjust the size down since vmbus_channel_packet_page_buffer is the
|
||||||
* largest size we support
|
* largest size we support
|
||||||
|
@ -742,8 +750,7 @@ int vmbus_sendpacket_pagebuffer_ctl(struct vmbus_channel *channel,
|
||||||
bufferlist[2].iov_base = &aligned_data;
|
bufferlist[2].iov_base = &aligned_data;
|
||||||
bufferlist[2].iov_len = (packetlen_aligned - packetlen);
|
bufferlist[2].iov_len = (packetlen_aligned - packetlen);
|
||||||
|
|
||||||
return hv_ringbuffer_write(channel, bufferlist, 3,
|
return hv_ringbuffer_write(channel, bufferlist, 3);
|
||||||
lock, kick_q);
|
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(vmbus_sendpacket_pagebuffer_ctl);
|
EXPORT_SYMBOL_GPL(vmbus_sendpacket_pagebuffer_ctl);
|
||||||
|
|
||||||
|
@ -757,9 +764,10 @@ int vmbus_sendpacket_pagebuffer(struct vmbus_channel *channel,
|
||||||
u64 requestid)
|
u64 requestid)
|
||||||
{
|
{
|
||||||
u32 flags = VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED;
|
u32 flags = VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED;
|
||||||
|
|
||||||
return vmbus_sendpacket_pagebuffer_ctl(channel, pagebuffers, pagecount,
|
return vmbus_sendpacket_pagebuffer_ctl(channel, pagebuffers, pagecount,
|
||||||
buffer, bufferlen, requestid,
|
buffer, bufferlen,
|
||||||
flags, true);
|
requestid, flags);
|
||||||
|
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(vmbus_sendpacket_pagebuffer);
|
EXPORT_SYMBOL_GPL(vmbus_sendpacket_pagebuffer);
|
||||||
|
@ -778,7 +786,6 @@ int vmbus_sendpacket_mpb_desc(struct vmbus_channel *channel,
|
||||||
u32 packetlen_aligned;
|
u32 packetlen_aligned;
|
||||||
struct kvec bufferlist[3];
|
struct kvec bufferlist[3];
|
||||||
u64 aligned_data = 0;
|
u64 aligned_data = 0;
|
||||||
bool lock = channel->acquire_ring_lock;
|
|
||||||
|
|
||||||
packetlen = desc_size + bufferlen;
|
packetlen = desc_size + bufferlen;
|
||||||
packetlen_aligned = ALIGN(packetlen, sizeof(u64));
|
packetlen_aligned = ALIGN(packetlen, sizeof(u64));
|
||||||
|
@ -798,8 +805,7 @@ int vmbus_sendpacket_mpb_desc(struct vmbus_channel *channel,
|
||||||
bufferlist[2].iov_base = &aligned_data;
|
bufferlist[2].iov_base = &aligned_data;
|
||||||
bufferlist[2].iov_len = (packetlen_aligned - packetlen);
|
bufferlist[2].iov_len = (packetlen_aligned - packetlen);
|
||||||
|
|
||||||
return hv_ringbuffer_write(channel, bufferlist, 3,
|
return hv_ringbuffer_write(channel, bufferlist, 3);
|
||||||
lock, true);
|
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(vmbus_sendpacket_mpb_desc);
|
EXPORT_SYMBOL_GPL(vmbus_sendpacket_mpb_desc);
|
||||||
|
|
||||||
|
@ -817,7 +823,6 @@ int vmbus_sendpacket_multipagebuffer(struct vmbus_channel *channel,
|
||||||
u32 packetlen_aligned;
|
u32 packetlen_aligned;
|
||||||
struct kvec bufferlist[3];
|
struct kvec bufferlist[3];
|
||||||
u64 aligned_data = 0;
|
u64 aligned_data = 0;
|
||||||
bool lock = channel->acquire_ring_lock;
|
|
||||||
u32 pfncount = NUM_PAGES_SPANNED(multi_pagebuffer->offset,
|
u32 pfncount = NUM_PAGES_SPANNED(multi_pagebuffer->offset,
|
||||||
multi_pagebuffer->len);
|
multi_pagebuffer->len);
|
||||||
|
|
||||||
|
@ -856,8 +861,7 @@ int vmbus_sendpacket_multipagebuffer(struct vmbus_channel *channel,
|
||||||
bufferlist[2].iov_base = &aligned_data;
|
bufferlist[2].iov_base = &aligned_data;
|
||||||
bufferlist[2].iov_len = (packetlen_aligned - packetlen);
|
bufferlist[2].iov_len = (packetlen_aligned - packetlen);
|
||||||
|
|
||||||
return hv_ringbuffer_write(channel, bufferlist, 3,
|
return hv_ringbuffer_write(channel, bufferlist, 3);
|
||||||
lock, true);
|
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(vmbus_sendpacket_multipagebuffer);
|
EXPORT_SYMBOL_GPL(vmbus_sendpacket_multipagebuffer);
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,7 @@
|
||||||
#include <linux/completion.h>
|
#include <linux/completion.h>
|
||||||
#include <linux/delay.h>
|
#include <linux/delay.h>
|
||||||
#include <linux/hyperv.h>
|
#include <linux/hyperv.h>
|
||||||
|
#include <asm/mshyperv.h>
|
||||||
|
|
||||||
#include "hyperv_vmbus.h"
|
#include "hyperv_vmbus.h"
|
||||||
|
|
||||||
|
@ -147,6 +148,29 @@ static const struct {
|
||||||
{ HV_RDV_GUID },
|
{ HV_RDV_GUID },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The rescinded channel may be blocked waiting for a response from the host;
|
||||||
|
* take care of that.
|
||||||
|
*/
|
||||||
|
static void vmbus_rescind_cleanup(struct vmbus_channel *channel)
|
||||||
|
{
|
||||||
|
struct vmbus_channel_msginfo *msginfo;
|
||||||
|
unsigned long flags;
|
||||||
|
|
||||||
|
|
||||||
|
spin_lock_irqsave(&vmbus_connection.channelmsg_lock, flags);
|
||||||
|
|
||||||
|
list_for_each_entry(msginfo, &vmbus_connection.chn_msg_list,
|
||||||
|
msglistentry) {
|
||||||
|
|
||||||
|
if (msginfo->waiting_channel == channel) {
|
||||||
|
complete(&msginfo->waitevent);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock, flags);
|
||||||
|
}
|
||||||
|
|
||||||
static bool is_unsupported_vmbus_devs(const uuid_le *guid)
|
static bool is_unsupported_vmbus_devs(const uuid_le *guid)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
@ -180,33 +204,34 @@ static u16 hv_get_dev_type(const struct vmbus_channel *channel)
|
||||||
* @buf: Raw buffer channel data
|
* @buf: Raw buffer channel data
|
||||||
*
|
*
|
||||||
* @icmsghdrp is of type &struct icmsg_hdr.
|
* @icmsghdrp is of type &struct icmsg_hdr.
|
||||||
* @negop is of type &struct icmsg_negotiate.
|
|
||||||
* Set up and fill in default negotiate response message.
|
* Set up and fill in default negotiate response message.
|
||||||
*
|
*
|
||||||
* The fw_version specifies the framework version that
|
* The fw_version and fw_vercnt specifies the framework version that
|
||||||
* we can support and srv_version specifies the service
|
* we can support.
|
||||||
* version we can support.
|
*
|
||||||
|
* The srv_version and srv_vercnt specifies the service
|
||||||
|
* versions we can support.
|
||||||
|
*
|
||||||
|
* Versions are given in decreasing order.
|
||||||
|
*
|
||||||
|
* nego_fw_version and nego_srv_version store the selected protocol versions.
|
||||||
*
|
*
|
||||||
* Mainly used by Hyper-V drivers.
|
* Mainly used by Hyper-V drivers.
|
||||||
*/
|
*/
|
||||||
bool vmbus_prep_negotiate_resp(struct icmsg_hdr *icmsghdrp,
|
bool vmbus_prep_negotiate_resp(struct icmsg_hdr *icmsghdrp,
|
||||||
struct icmsg_negotiate *negop, u8 *buf,
|
u8 *buf, const int *fw_version, int fw_vercnt,
|
||||||
int fw_version, int srv_version)
|
const int *srv_version, int srv_vercnt,
|
||||||
|
int *nego_fw_version, int *nego_srv_version)
|
||||||
{
|
{
|
||||||
int icframe_major, icframe_minor;
|
int icframe_major, icframe_minor;
|
||||||
int icmsg_major, icmsg_minor;
|
int icmsg_major, icmsg_minor;
|
||||||
int fw_major, fw_minor;
|
int fw_major, fw_minor;
|
||||||
int srv_major, srv_minor;
|
int srv_major, srv_minor;
|
||||||
int i;
|
int i, j;
|
||||||
bool found_match = false;
|
bool found_match = false;
|
||||||
|
struct icmsg_negotiate *negop;
|
||||||
|
|
||||||
icmsghdrp->icmsgsize = 0x10;
|
icmsghdrp->icmsgsize = 0x10;
|
||||||
fw_major = (fw_version >> 16);
|
|
||||||
fw_minor = (fw_version & 0xFFFF);
|
|
||||||
|
|
||||||
srv_major = (srv_version >> 16);
|
|
||||||
srv_minor = (srv_version & 0xFFFF);
|
|
||||||
|
|
||||||
negop = (struct icmsg_negotiate *)&buf[
|
negop = (struct icmsg_negotiate *)&buf[
|
||||||
sizeof(struct vmbuspipe_hdr) +
|
sizeof(struct vmbuspipe_hdr) +
|
||||||
sizeof(struct icmsg_hdr)];
|
sizeof(struct icmsg_hdr)];
|
||||||
|
@ -222,30 +247,51 @@ bool vmbus_prep_negotiate_resp(struct icmsg_hdr *icmsghdrp,
|
||||||
* support.
|
* support.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
for (i = 0; i < negop->icframe_vercnt; i++) {
|
for (i = 0; i < fw_vercnt; i++) {
|
||||||
if ((negop->icversion_data[i].major == fw_major) &&
|
fw_major = (fw_version[i] >> 16);
|
||||||
(negop->icversion_data[i].minor == fw_minor)) {
|
fw_minor = (fw_version[i] & 0xFFFF);
|
||||||
icframe_major = negop->icversion_data[i].major;
|
|
||||||
icframe_minor = negop->icversion_data[i].minor;
|
for (j = 0; j < negop->icframe_vercnt; j++) {
|
||||||
|
if ((negop->icversion_data[j].major == fw_major) &&
|
||||||
|
(negop->icversion_data[j].minor == fw_minor)) {
|
||||||
|
icframe_major = negop->icversion_data[j].major;
|
||||||
|
icframe_minor = negop->icversion_data[j].minor;
|
||||||
found_match = true;
|
found_match = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (found_match)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (!found_match)
|
if (!found_match)
|
||||||
goto fw_error;
|
goto fw_error;
|
||||||
|
|
||||||
found_match = false;
|
found_match = false;
|
||||||
|
|
||||||
for (i = negop->icframe_vercnt;
|
for (i = 0; i < srv_vercnt; i++) {
|
||||||
(i < negop->icframe_vercnt + negop->icmsg_vercnt); i++) {
|
srv_major = (srv_version[i] >> 16);
|
||||||
if ((negop->icversion_data[i].major == srv_major) &&
|
srv_minor = (srv_version[i] & 0xFFFF);
|
||||||
(negop->icversion_data[i].minor == srv_minor)) {
|
|
||||||
icmsg_major = negop->icversion_data[i].major;
|
for (j = negop->icframe_vercnt;
|
||||||
icmsg_minor = negop->icversion_data[i].minor;
|
(j < negop->icframe_vercnt + negop->icmsg_vercnt);
|
||||||
|
j++) {
|
||||||
|
|
||||||
|
if ((negop->icversion_data[j].major == srv_major) &&
|
||||||
|
(negop->icversion_data[j].minor == srv_minor)) {
|
||||||
|
|
||||||
|
icmsg_major = negop->icversion_data[j].major;
|
||||||
|
icmsg_minor = negop->icversion_data[j].minor;
|
||||||
found_match = true;
|
found_match = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (found_match)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Respond with the framework and service
|
* Respond with the framework and service
|
||||||
* version numbers we can support.
|
* version numbers we can support.
|
||||||
|
@ -260,6 +306,12 @@ bool vmbus_prep_negotiate_resp(struct icmsg_hdr *icmsghdrp,
|
||||||
negop->icmsg_vercnt = 1;
|
negop->icmsg_vercnt = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (nego_fw_version)
|
||||||
|
*nego_fw_version = (icframe_major << 16) | icframe_minor;
|
||||||
|
|
||||||
|
if (nego_srv_version)
|
||||||
|
*nego_srv_version = (icmsg_major << 16) | icmsg_minor;
|
||||||
|
|
||||||
negop->icversion_data[0].major = icframe_major;
|
negop->icversion_data[0].major = icframe_major;
|
||||||
negop->icversion_data[0].minor = icframe_minor;
|
negop->icversion_data[0].minor = icframe_minor;
|
||||||
negop->icversion_data[1].major = icmsg_major;
|
negop->icversion_data[1].major = icmsg_major;
|
||||||
|
@ -280,13 +332,15 @@ static struct vmbus_channel *alloc_channel(void)
|
||||||
if (!channel)
|
if (!channel)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
channel->acquire_ring_lock = true;
|
|
||||||
spin_lock_init(&channel->inbound_lock);
|
spin_lock_init(&channel->inbound_lock);
|
||||||
spin_lock_init(&channel->lock);
|
spin_lock_init(&channel->lock);
|
||||||
|
|
||||||
INIT_LIST_HEAD(&channel->sc_list);
|
INIT_LIST_HEAD(&channel->sc_list);
|
||||||
INIT_LIST_HEAD(&channel->percpu_list);
|
INIT_LIST_HEAD(&channel->percpu_list);
|
||||||
|
|
||||||
|
tasklet_init(&channel->callback_event,
|
||||||
|
vmbus_on_event, (unsigned long)channel);
|
||||||
|
|
||||||
return channel;
|
return channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -295,15 +349,17 @@ static struct vmbus_channel *alloc_channel(void)
|
||||||
*/
|
*/
|
||||||
static void free_channel(struct vmbus_channel *channel)
|
static void free_channel(struct vmbus_channel *channel)
|
||||||
{
|
{
|
||||||
|
tasklet_kill(&channel->callback_event);
|
||||||
kfree(channel);
|
kfree(channel);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void percpu_channel_enq(void *arg)
|
static void percpu_channel_enq(void *arg)
|
||||||
{
|
{
|
||||||
struct vmbus_channel *channel = arg;
|
struct vmbus_channel *channel = arg;
|
||||||
int cpu = smp_processor_id();
|
struct hv_per_cpu_context *hv_cpu
|
||||||
|
= this_cpu_ptr(hv_context.cpu_context);
|
||||||
|
|
||||||
list_add_tail(&channel->percpu_list, &hv_context.percpu_list[cpu]);
|
list_add_tail(&channel->percpu_list, &hv_cpu->chan_list);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void percpu_channel_deq(void *arg)
|
static void percpu_channel_deq(void *arg)
|
||||||
|
@ -321,24 +377,21 @@ static void vmbus_release_relid(u32 relid)
|
||||||
memset(&msg, 0, sizeof(struct vmbus_channel_relid_released));
|
memset(&msg, 0, sizeof(struct vmbus_channel_relid_released));
|
||||||
msg.child_relid = relid;
|
msg.child_relid = relid;
|
||||||
msg.header.msgtype = CHANNELMSG_RELID_RELEASED;
|
msg.header.msgtype = CHANNELMSG_RELID_RELEASED;
|
||||||
vmbus_post_msg(&msg, sizeof(struct vmbus_channel_relid_released));
|
vmbus_post_msg(&msg, sizeof(struct vmbus_channel_relid_released),
|
||||||
|
true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void hv_event_tasklet_disable(struct vmbus_channel *channel)
|
void hv_event_tasklet_disable(struct vmbus_channel *channel)
|
||||||
{
|
{
|
||||||
struct tasklet_struct *tasklet;
|
tasklet_disable(&channel->callback_event);
|
||||||
tasklet = hv_context.event_dpc[channel->target_cpu];
|
|
||||||
tasklet_disable(tasklet);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void hv_event_tasklet_enable(struct vmbus_channel *channel)
|
void hv_event_tasklet_enable(struct vmbus_channel *channel)
|
||||||
{
|
{
|
||||||
struct tasklet_struct *tasklet;
|
tasklet_enable(&channel->callback_event);
|
||||||
tasklet = hv_context.event_dpc[channel->target_cpu];
|
|
||||||
tasklet_enable(tasklet);
|
|
||||||
|
|
||||||
/* In case there is any pending event */
|
/* In case there is any pending event */
|
||||||
tasklet_schedule(tasklet);
|
tasklet_schedule(&channel->callback_event);
|
||||||
}
|
}
|
||||||
|
|
||||||
void hv_process_channel_removal(struct vmbus_channel *channel, u32 relid)
|
void hv_process_channel_removal(struct vmbus_channel *channel, u32 relid)
|
||||||
|
@ -673,9 +726,12 @@ static void vmbus_wait_for_unload(void)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
for_each_online_cpu(cpu) {
|
for_each_online_cpu(cpu) {
|
||||||
page_addr = hv_context.synic_message_page[cpu];
|
struct hv_per_cpu_context *hv_cpu
|
||||||
msg = (struct hv_message *)page_addr +
|
= per_cpu_ptr(hv_context.cpu_context, cpu);
|
||||||
VMBUS_MESSAGE_SINT;
|
|
||||||
|
page_addr = hv_cpu->synic_message_page;
|
||||||
|
msg = (struct hv_message *)page_addr
|
||||||
|
+ VMBUS_MESSAGE_SINT;
|
||||||
|
|
||||||
message_type = READ_ONCE(msg->header.message_type);
|
message_type = READ_ONCE(msg->header.message_type);
|
||||||
if (message_type == HVMSG_NONE)
|
if (message_type == HVMSG_NONE)
|
||||||
|
@ -699,7 +755,10 @@ static void vmbus_wait_for_unload(void)
|
||||||
* messages after we reconnect.
|
* messages after we reconnect.
|
||||||
*/
|
*/
|
||||||
for_each_online_cpu(cpu) {
|
for_each_online_cpu(cpu) {
|
||||||
page_addr = hv_context.synic_message_page[cpu];
|
struct hv_per_cpu_context *hv_cpu
|
||||||
|
= per_cpu_ptr(hv_context.cpu_context, cpu);
|
||||||
|
|
||||||
|
page_addr = hv_cpu->synic_message_page;
|
||||||
msg = (struct hv_message *)page_addr + VMBUS_MESSAGE_SINT;
|
msg = (struct hv_message *)page_addr + VMBUS_MESSAGE_SINT;
|
||||||
msg->header.message_type = HVMSG_NONE;
|
msg->header.message_type = HVMSG_NONE;
|
||||||
}
|
}
|
||||||
|
@ -728,7 +787,8 @@ void vmbus_initiate_unload(bool crash)
|
||||||
init_completion(&vmbus_connection.unload_event);
|
init_completion(&vmbus_connection.unload_event);
|
||||||
memset(&hdr, 0, sizeof(struct vmbus_channel_message_header));
|
memset(&hdr, 0, sizeof(struct vmbus_channel_message_header));
|
||||||
hdr.msgtype = CHANNELMSG_UNLOAD;
|
hdr.msgtype = CHANNELMSG_UNLOAD;
|
||||||
vmbus_post_msg(&hdr, sizeof(struct vmbus_channel_message_header));
|
vmbus_post_msg(&hdr, sizeof(struct vmbus_channel_message_header),
|
||||||
|
!crash);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* vmbus_initiate_unload() is also called on crash and the crash can be
|
* vmbus_initiate_unload() is also called on crash and the crash can be
|
||||||
|
@ -758,13 +818,6 @@ static void vmbus_onoffer(struct vmbus_channel_message_header *hdr)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* By default we setup state to enable batched
|
|
||||||
* reading. A specific service can choose to
|
|
||||||
* disable this prior to opening the channel.
|
|
||||||
*/
|
|
||||||
newchannel->batched_reading = true;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Setup state for signalling the host.
|
* Setup state for signalling the host.
|
||||||
*/
|
*/
|
||||||
|
@ -823,6 +876,8 @@ static void vmbus_onoffer_rescind(struct vmbus_channel_message_header *hdr)
|
||||||
channel->rescind = true;
|
channel->rescind = true;
|
||||||
spin_unlock_irqrestore(&channel->lock, flags);
|
spin_unlock_irqrestore(&channel->lock, flags);
|
||||||
|
|
||||||
|
vmbus_rescind_cleanup(channel);
|
||||||
|
|
||||||
if (channel->device_obj) {
|
if (channel->device_obj) {
|
||||||
if (channel->chn_rescind_callback) {
|
if (channel->chn_rescind_callback) {
|
||||||
channel->chn_rescind_callback(channel);
|
channel->chn_rescind_callback(channel);
|
||||||
|
@ -1116,8 +1171,8 @@ int vmbus_request_offers(void)
|
||||||
msg->msgtype = CHANNELMSG_REQUESTOFFERS;
|
msg->msgtype = CHANNELMSG_REQUESTOFFERS;
|
||||||
|
|
||||||
|
|
||||||
ret = vmbus_post_msg(msg,
|
ret = vmbus_post_msg(msg, sizeof(struct vmbus_channel_message_header),
|
||||||
sizeof(struct vmbus_channel_message_header));
|
true);
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
pr_err("Unable to request offers - %d\n", ret);
|
pr_err("Unable to request offers - %d\n", ret);
|
||||||
|
|
||||||
|
|
|
@ -93,12 +93,10 @@ static int vmbus_negotiate_version(struct vmbus_channel_msginfo *msginfo,
|
||||||
* all the CPUs. This is needed for kexec to work correctly where
|
* all the CPUs. This is needed for kexec to work correctly where
|
||||||
* the CPU attempting to connect may not be CPU 0.
|
* the CPU attempting to connect may not be CPU 0.
|
||||||
*/
|
*/
|
||||||
if (version >= VERSION_WIN8_1) {
|
if (version >= VERSION_WIN8_1)
|
||||||
msg->target_vcpu = hv_context.vp_index[get_cpu()];
|
msg->target_vcpu = hv_context.vp_index[smp_processor_id()];
|
||||||
put_cpu();
|
else
|
||||||
} else {
|
|
||||||
msg->target_vcpu = 0;
|
msg->target_vcpu = 0;
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Add to list before we send the request since we may
|
* Add to list before we send the request since we may
|
||||||
|
@ -111,7 +109,8 @@ static int vmbus_negotiate_version(struct vmbus_channel_msginfo *msginfo,
|
||||||
spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock, flags);
|
spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock, flags);
|
||||||
|
|
||||||
ret = vmbus_post_msg(msg,
|
ret = vmbus_post_msg(msg,
|
||||||
sizeof(struct vmbus_channel_initiate_contact));
|
sizeof(struct vmbus_channel_initiate_contact),
|
||||||
|
true);
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
spin_lock_irqsave(&vmbus_connection.channelmsg_lock, flags);
|
spin_lock_irqsave(&vmbus_connection.channelmsg_lock, flags);
|
||||||
list_del(&msginfo->msglistentry);
|
list_del(&msginfo->msglistentry);
|
||||||
|
@ -220,10 +219,7 @@ int vmbus_connect(void)
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
|
|
||||||
vmbus_proto_version = version;
|
vmbus_proto_version = version;
|
||||||
pr_info("Hyper-V Host Build:%d-%d.%d-%d-%d.%d; Vmbus version:%d.%d\n",
|
pr_info("Vmbus version:%d.%d\n",
|
||||||
host_info_eax, host_info_ebx >> 16,
|
|
||||||
host_info_ebx & 0xFFFF, host_info_ecx,
|
|
||||||
host_info_edx >> 24, host_info_edx & 0xFFFFFF,
|
|
||||||
version >> 16, version & 0xFFFF);
|
version >> 16, version & 0xFFFF);
|
||||||
|
|
||||||
kfree(msginfo);
|
kfree(msginfo);
|
||||||
|
@ -263,29 +259,6 @@ void vmbus_disconnect(void)
|
||||||
vmbus_connection.monitor_pages[1] = NULL;
|
vmbus_connection.monitor_pages[1] = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Map the given relid to the corresponding channel based on the
|
|
||||||
* per-cpu list of channels that have been affinitized to this CPU.
|
|
||||||
* This will be used in the channel callback path as we can do this
|
|
||||||
* mapping in a lock-free fashion.
|
|
||||||
*/
|
|
||||||
static struct vmbus_channel *pcpu_relid2channel(u32 relid)
|
|
||||||
{
|
|
||||||
struct vmbus_channel *channel;
|
|
||||||
struct vmbus_channel *found_channel = NULL;
|
|
||||||
int cpu = smp_processor_id();
|
|
||||||
struct list_head *pcpu_head = &hv_context.percpu_list[cpu];
|
|
||||||
|
|
||||||
list_for_each_entry(channel, pcpu_head, percpu_list) {
|
|
||||||
if (channel->offermsg.child_relid == relid) {
|
|
||||||
found_channel = channel;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return found_channel;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* relid2channel - Get the channel object given its
|
* relid2channel - Get the channel object given its
|
||||||
* child relative id (ie channel id)
|
* child relative id (ie channel id)
|
||||||
|
@ -322,23 +295,12 @@ struct vmbus_channel *relid2channel(u32 relid)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* process_chn_event - Process a channel event notification
|
* vmbus_on_event - Process a channel event notification
|
||||||
*/
|
*/
|
||||||
static void process_chn_event(u32 relid)
|
void vmbus_on_event(unsigned long data)
|
||||||
{
|
{
|
||||||
struct vmbus_channel *channel;
|
struct vmbus_channel *channel = (void *) data;
|
||||||
void *arg;
|
void (*callback_fn)(void *);
|
||||||
bool read_state;
|
|
||||||
u32 bytes_to_read;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Find the channel based on this relid and invokes the
|
|
||||||
* channel callback to process the event
|
|
||||||
*/
|
|
||||||
channel = pcpu_relid2channel(relid);
|
|
||||||
|
|
||||||
if (!channel)
|
|
||||||
return;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* A channel once created is persistent even when there
|
* A channel once created is persistent even when there
|
||||||
|
@ -348,10 +310,13 @@ static void process_chn_event(u32 relid)
|
||||||
* Thus, checking and invoking the driver specific callback takes
|
* Thus, checking and invoking the driver specific callback takes
|
||||||
* care of orderly unloading of the driver.
|
* care of orderly unloading of the driver.
|
||||||
*/
|
*/
|
||||||
|
callback_fn = READ_ONCE(channel->onchannel_callback);
|
||||||
|
if (unlikely(callback_fn == NULL))
|
||||||
|
return;
|
||||||
|
|
||||||
if (channel->onchannel_callback != NULL) {
|
(*callback_fn)(channel->channel_callback_context);
|
||||||
arg = channel->channel_callback_context;
|
|
||||||
read_state = channel->batched_reading;
|
if (channel->callback_mode == HV_CALL_BATCHED) {
|
||||||
/*
|
/*
|
||||||
* This callback reads the messages sent by the host.
|
* This callback reads the messages sent by the host.
|
||||||
* We can optimize host to guest signaling by ensuring:
|
* We can optimize host to guest signaling by ensuring:
|
||||||
|
@ -363,71 +328,10 @@ static void process_chn_event(u32 relid)
|
||||||
* state is set we check to see if additional packets are
|
* state is set we check to see if additional packets are
|
||||||
* available to read. In this case we repeat the process.
|
* available to read. In this case we repeat the process.
|
||||||
*/
|
*/
|
||||||
|
if (hv_end_read(&channel->inbound) != 0) {
|
||||||
do {
|
|
||||||
if (read_state)
|
|
||||||
hv_begin_read(&channel->inbound);
|
hv_begin_read(&channel->inbound);
|
||||||
channel->onchannel_callback(arg);
|
|
||||||
if (read_state)
|
|
||||||
bytes_to_read = hv_end_read(&channel->inbound);
|
|
||||||
else
|
|
||||||
bytes_to_read = 0;
|
|
||||||
} while (read_state && (bytes_to_read != 0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
tasklet_schedule(&channel->callback_event);
|
||||||
* vmbus_on_event - Handler for events
|
|
||||||
*/
|
|
||||||
void vmbus_on_event(unsigned long data)
|
|
||||||
{
|
|
||||||
u32 dword;
|
|
||||||
u32 maxdword;
|
|
||||||
int bit;
|
|
||||||
u32 relid;
|
|
||||||
u32 *recv_int_page = NULL;
|
|
||||||
void *page_addr;
|
|
||||||
int cpu = smp_processor_id();
|
|
||||||
union hv_synic_event_flags *event;
|
|
||||||
|
|
||||||
if (vmbus_proto_version < VERSION_WIN8) {
|
|
||||||
maxdword = MAX_NUM_CHANNELS_SUPPORTED >> 5;
|
|
||||||
recv_int_page = vmbus_connection.recv_int_page;
|
|
||||||
} else {
|
|
||||||
/*
|
|
||||||
* When the host is win8 and beyond, the event page
|
|
||||||
* can be directly checked to get the id of the channel
|
|
||||||
* that has the interrupt pending.
|
|
||||||
*/
|
|
||||||
maxdword = HV_EVENT_FLAGS_DWORD_COUNT;
|
|
||||||
page_addr = hv_context.synic_event_page[cpu];
|
|
||||||
event = (union hv_synic_event_flags *)page_addr +
|
|
||||||
VMBUS_MESSAGE_SINT;
|
|
||||||
recv_int_page = event->flags32;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Check events */
|
|
||||||
if (!recv_int_page)
|
|
||||||
return;
|
|
||||||
for (dword = 0; dword < maxdword; dword++) {
|
|
||||||
if (!recv_int_page[dword])
|
|
||||||
continue;
|
|
||||||
for (bit = 0; bit < 32; bit++) {
|
|
||||||
if (sync_test_and_clear_bit(bit,
|
|
||||||
(unsigned long *)&recv_int_page[dword])) {
|
|
||||||
relid = (dword << 5) + bit;
|
|
||||||
|
|
||||||
if (relid == 0)
|
|
||||||
/*
|
|
||||||
* Special case - vmbus
|
|
||||||
* channel protocol msg
|
|
||||||
*/
|
|
||||||
continue;
|
|
||||||
|
|
||||||
process_chn_event(relid);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -435,7 +339,7 @@ void vmbus_on_event(unsigned long data)
|
||||||
/*
|
/*
|
||||||
* vmbus_post_msg - Send a msg on the vmbus's message connection
|
* vmbus_post_msg - Send a msg on the vmbus's message connection
|
||||||
*/
|
*/
|
||||||
int vmbus_post_msg(void *buffer, size_t buflen)
|
int vmbus_post_msg(void *buffer, size_t buflen, bool can_sleep)
|
||||||
{
|
{
|
||||||
union hv_connection_id conn_id;
|
union hv_connection_id conn_id;
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
|
@ -450,7 +354,7 @@ int vmbus_post_msg(void *buffer, size_t buflen)
|
||||||
* insufficient resources. Retry the operation a couple of
|
* insufficient resources. Retry the operation a couple of
|
||||||
* times before giving up.
|
* times before giving up.
|
||||||
*/
|
*/
|
||||||
while (retries < 20) {
|
while (retries < 100) {
|
||||||
ret = hv_post_message(conn_id, 1, buffer, buflen);
|
ret = hv_post_message(conn_id, 1, buffer, buflen);
|
||||||
|
|
||||||
switch (ret) {
|
switch (ret) {
|
||||||
|
@ -473,8 +377,14 @@ int vmbus_post_msg(void *buffer, size_t buflen)
|
||||||
}
|
}
|
||||||
|
|
||||||
retries++;
|
retries++;
|
||||||
|
if (can_sleep && usec > 1000)
|
||||||
|
msleep(usec / 1000);
|
||||||
|
else if (usec < MAX_UDELAY_MS * 1000)
|
||||||
udelay(usec);
|
udelay(usec);
|
||||||
if (usec < 2048)
|
else
|
||||||
|
mdelay(usec / 1000);
|
||||||
|
|
||||||
|
if (usec < 256000)
|
||||||
usec *= 2;
|
usec *= 2;
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -487,12 +397,8 @@ void vmbus_set_event(struct vmbus_channel *channel)
|
||||||
{
|
{
|
||||||
u32 child_relid = channel->offermsg.child_relid;
|
u32 child_relid = channel->offermsg.child_relid;
|
||||||
|
|
||||||
if (!channel->is_dedicated_interrupt) {
|
if (!channel->is_dedicated_interrupt)
|
||||||
/* Each u32 represents 32 channels */
|
vmbus_send_interrupt(child_relid);
|
||||||
sync_set_bit(child_relid & 31,
|
|
||||||
(unsigned long *)vmbus_connection.send_int_page +
|
|
||||||
(child_relid >> 5));
|
|
||||||
}
|
|
||||||
|
|
||||||
hv_do_hypercall(HVCALL_SIGNAL_EVENT, channel->sig_event, NULL);
|
hv_do_hypercall(HVCALL_SIGNAL_EVENT, channel->sig_event, NULL);
|
||||||
}
|
}
|
||||||
|
|
475
drivers/hv/hv.c
475
drivers/hv/hv.c
|
@ -36,154 +36,12 @@
|
||||||
/* The one and only */
|
/* The one and only */
|
||||||
struct hv_context hv_context = {
|
struct hv_context hv_context = {
|
||||||
.synic_initialized = false,
|
.synic_initialized = false,
|
||||||
.hypercall_page = NULL,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#define HV_TIMER_FREQUENCY (10 * 1000 * 1000) /* 100ns period */
|
#define HV_TIMER_FREQUENCY (10 * 1000 * 1000) /* 100ns period */
|
||||||
#define HV_MAX_MAX_DELTA_TICKS 0xffffffff
|
#define HV_MAX_MAX_DELTA_TICKS 0xffffffff
|
||||||
#define HV_MIN_DELTA_TICKS 1
|
#define HV_MIN_DELTA_TICKS 1
|
||||||
|
|
||||||
/*
|
|
||||||
* query_hypervisor_info - Get version info of the windows hypervisor
|
|
||||||
*/
|
|
||||||
unsigned int host_info_eax;
|
|
||||||
unsigned int host_info_ebx;
|
|
||||||
unsigned int host_info_ecx;
|
|
||||||
unsigned int host_info_edx;
|
|
||||||
|
|
||||||
static int query_hypervisor_info(void)
|
|
||||||
{
|
|
||||||
unsigned int eax;
|
|
||||||
unsigned int ebx;
|
|
||||||
unsigned int ecx;
|
|
||||||
unsigned int edx;
|
|
||||||
unsigned int max_leaf;
|
|
||||||
unsigned int op;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Its assumed that this is called after confirming that Viridian
|
|
||||||
* is present. Query id and revision.
|
|
||||||
*/
|
|
||||||
eax = 0;
|
|
||||||
ebx = 0;
|
|
||||||
ecx = 0;
|
|
||||||
edx = 0;
|
|
||||||
op = HVCPUID_VENDOR_MAXFUNCTION;
|
|
||||||
cpuid(op, &eax, &ebx, &ecx, &edx);
|
|
||||||
|
|
||||||
max_leaf = eax;
|
|
||||||
|
|
||||||
if (max_leaf >= HVCPUID_VERSION) {
|
|
||||||
eax = 0;
|
|
||||||
ebx = 0;
|
|
||||||
ecx = 0;
|
|
||||||
edx = 0;
|
|
||||||
op = HVCPUID_VERSION;
|
|
||||||
cpuid(op, &eax, &ebx, &ecx, &edx);
|
|
||||||
host_info_eax = eax;
|
|
||||||
host_info_ebx = ebx;
|
|
||||||
host_info_ecx = ecx;
|
|
||||||
host_info_edx = edx;
|
|
||||||
}
|
|
||||||
return max_leaf;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* hv_do_hypercall- Invoke the specified hypercall
|
|
||||||
*/
|
|
||||||
u64 hv_do_hypercall(u64 control, void *input, void *output)
|
|
||||||
{
|
|
||||||
u64 input_address = (input) ? virt_to_phys(input) : 0;
|
|
||||||
u64 output_address = (output) ? virt_to_phys(output) : 0;
|
|
||||||
void *hypercall_page = hv_context.hypercall_page;
|
|
||||||
#ifdef CONFIG_X86_64
|
|
||||||
u64 hv_status = 0;
|
|
||||||
|
|
||||||
if (!hypercall_page)
|
|
||||||
return (u64)ULLONG_MAX;
|
|
||||||
|
|
||||||
__asm__ __volatile__("mov %0, %%r8" : : "r" (output_address) : "r8");
|
|
||||||
__asm__ __volatile__("call *%3" : "=a" (hv_status) :
|
|
||||||
"c" (control), "d" (input_address),
|
|
||||||
"m" (hypercall_page));
|
|
||||||
|
|
||||||
return hv_status;
|
|
||||||
|
|
||||||
#else
|
|
||||||
|
|
||||||
u32 control_hi = control >> 32;
|
|
||||||
u32 control_lo = control & 0xFFFFFFFF;
|
|
||||||
u32 hv_status_hi = 1;
|
|
||||||
u32 hv_status_lo = 1;
|
|
||||||
u32 input_address_hi = input_address >> 32;
|
|
||||||
u32 input_address_lo = input_address & 0xFFFFFFFF;
|
|
||||||
u32 output_address_hi = output_address >> 32;
|
|
||||||
u32 output_address_lo = output_address & 0xFFFFFFFF;
|
|
||||||
|
|
||||||
if (!hypercall_page)
|
|
||||||
return (u64)ULLONG_MAX;
|
|
||||||
|
|
||||||
__asm__ __volatile__ ("call *%8" : "=d"(hv_status_hi),
|
|
||||||
"=a"(hv_status_lo) : "d" (control_hi),
|
|
||||||
"a" (control_lo), "b" (input_address_hi),
|
|
||||||
"c" (input_address_lo), "D"(output_address_hi),
|
|
||||||
"S"(output_address_lo), "m" (hypercall_page));
|
|
||||||
|
|
||||||
return hv_status_lo | ((u64)hv_status_hi << 32);
|
|
||||||
#endif /* !x86_64 */
|
|
||||||
}
|
|
||||||
EXPORT_SYMBOL_GPL(hv_do_hypercall);
|
|
||||||
|
|
||||||
#ifdef CONFIG_X86_64
|
|
||||||
static u64 read_hv_clock_tsc(struct clocksource *arg)
|
|
||||||
{
|
|
||||||
u64 current_tick;
|
|
||||||
struct ms_hyperv_tsc_page *tsc_pg = hv_context.tsc_page;
|
|
||||||
|
|
||||||
if (tsc_pg->tsc_sequence != 0) {
|
|
||||||
/*
|
|
||||||
* Use the tsc page to compute the value.
|
|
||||||
*/
|
|
||||||
|
|
||||||
while (1) {
|
|
||||||
u64 tmp;
|
|
||||||
u32 sequence = tsc_pg->tsc_sequence;
|
|
||||||
u64 cur_tsc;
|
|
||||||
u64 scale = tsc_pg->tsc_scale;
|
|
||||||
s64 offset = tsc_pg->tsc_offset;
|
|
||||||
|
|
||||||
rdtscll(cur_tsc);
|
|
||||||
/* current_tick = ((cur_tsc *scale) >> 64) + offset */
|
|
||||||
asm("mulq %3"
|
|
||||||
: "=d" (current_tick), "=a" (tmp)
|
|
||||||
: "a" (cur_tsc), "r" (scale));
|
|
||||||
|
|
||||||
current_tick += offset;
|
|
||||||
if (tsc_pg->tsc_sequence == sequence)
|
|
||||||
return current_tick;
|
|
||||||
|
|
||||||
if (tsc_pg->tsc_sequence != 0)
|
|
||||||
continue;
|
|
||||||
/*
|
|
||||||
* Fallback using MSR method.
|
|
||||||
*/
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rdmsrl(HV_X64_MSR_TIME_REF_COUNT, current_tick);
|
|
||||||
return current_tick;
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct clocksource hyperv_cs_tsc = {
|
|
||||||
.name = "hyperv_clocksource_tsc_page",
|
|
||||||
.rating = 425,
|
|
||||||
.read = read_hv_clock_tsc,
|
|
||||||
.mask = CLOCKSOURCE_MASK(64),
|
|
||||||
.flags = CLOCK_SOURCE_IS_CONTINUOUS,
|
|
||||||
};
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* hv_init - Main initialization routine.
|
* hv_init - Main initialization routine.
|
||||||
*
|
*
|
||||||
|
@ -191,129 +49,14 @@ static struct clocksource hyperv_cs_tsc = {
|
||||||
*/
|
*/
|
||||||
int hv_init(void)
|
int hv_init(void)
|
||||||
{
|
{
|
||||||
int max_leaf;
|
if (!hv_is_hypercall_page_setup())
|
||||||
union hv_x64_msr_hypercall_contents hypercall_msr;
|
|
||||||
void *virtaddr = NULL;
|
|
||||||
|
|
||||||
memset(hv_context.synic_event_page, 0, sizeof(void *) * NR_CPUS);
|
|
||||||
memset(hv_context.synic_message_page, 0,
|
|
||||||
sizeof(void *) * NR_CPUS);
|
|
||||||
memset(hv_context.post_msg_page, 0,
|
|
||||||
sizeof(void *) * NR_CPUS);
|
|
||||||
memset(hv_context.vp_index, 0,
|
|
||||||
sizeof(int) * NR_CPUS);
|
|
||||||
memset(hv_context.event_dpc, 0,
|
|
||||||
sizeof(void *) * NR_CPUS);
|
|
||||||
memset(hv_context.msg_dpc, 0,
|
|
||||||
sizeof(void *) * NR_CPUS);
|
|
||||||
memset(hv_context.clk_evt, 0,
|
|
||||||
sizeof(void *) * NR_CPUS);
|
|
||||||
|
|
||||||
max_leaf = query_hypervisor_info();
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Write our OS ID.
|
|
||||||
*/
|
|
||||||
hv_context.guestid = generate_guest_id(0, LINUX_VERSION_CODE, 0);
|
|
||||||
wrmsrl(HV_X64_MSR_GUEST_OS_ID, hv_context.guestid);
|
|
||||||
|
|
||||||
/* See if the hypercall page is already set */
|
|
||||||
rdmsrl(HV_X64_MSR_HYPERCALL, hypercall_msr.as_uint64);
|
|
||||||
|
|
||||||
virtaddr = __vmalloc(PAGE_SIZE, GFP_KERNEL, PAGE_KERNEL_EXEC);
|
|
||||||
|
|
||||||
if (!virtaddr)
|
|
||||||
goto cleanup;
|
|
||||||
|
|
||||||
hypercall_msr.enable = 1;
|
|
||||||
|
|
||||||
hypercall_msr.guest_physical_address = vmalloc_to_pfn(virtaddr);
|
|
||||||
wrmsrl(HV_X64_MSR_HYPERCALL, hypercall_msr.as_uint64);
|
|
||||||
|
|
||||||
/* Confirm that hypercall page did get setup. */
|
|
||||||
hypercall_msr.as_uint64 = 0;
|
|
||||||
rdmsrl(HV_X64_MSR_HYPERCALL, hypercall_msr.as_uint64);
|
|
||||||
|
|
||||||
if (!hypercall_msr.enable)
|
|
||||||
goto cleanup;
|
|
||||||
|
|
||||||
hv_context.hypercall_page = virtaddr;
|
|
||||||
|
|
||||||
#ifdef CONFIG_X86_64
|
|
||||||
if (ms_hyperv.features & HV_X64_MSR_REFERENCE_TSC_AVAILABLE) {
|
|
||||||
union hv_x64_msr_hypercall_contents tsc_msr;
|
|
||||||
void *va_tsc;
|
|
||||||
|
|
||||||
va_tsc = __vmalloc(PAGE_SIZE, GFP_KERNEL, PAGE_KERNEL);
|
|
||||||
if (!va_tsc)
|
|
||||||
goto cleanup;
|
|
||||||
hv_context.tsc_page = va_tsc;
|
|
||||||
|
|
||||||
rdmsrl(HV_X64_MSR_REFERENCE_TSC, tsc_msr.as_uint64);
|
|
||||||
|
|
||||||
tsc_msr.enable = 1;
|
|
||||||
tsc_msr.guest_physical_address = vmalloc_to_pfn(va_tsc);
|
|
||||||
|
|
||||||
wrmsrl(HV_X64_MSR_REFERENCE_TSC, tsc_msr.as_uint64);
|
|
||||||
clocksource_register_hz(&hyperv_cs_tsc, NSEC_PER_SEC/100);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
cleanup:
|
|
||||||
if (virtaddr) {
|
|
||||||
if (hypercall_msr.enable) {
|
|
||||||
hypercall_msr.as_uint64 = 0;
|
|
||||||
wrmsrl(HV_X64_MSR_HYPERCALL, hypercall_msr.as_uint64);
|
|
||||||
}
|
|
||||||
|
|
||||||
vfree(virtaddr);
|
|
||||||
}
|
|
||||||
|
|
||||||
return -ENOTSUPP;
|
return -ENOTSUPP;
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
hv_context.cpu_context = alloc_percpu(struct hv_per_cpu_context);
|
||||||
* hv_cleanup - Cleanup routine.
|
if (!hv_context.cpu_context)
|
||||||
*
|
return -ENOMEM;
|
||||||
* This routine is called normally during driver unloading or exiting.
|
|
||||||
*/
|
|
||||||
void hv_cleanup(bool crash)
|
|
||||||
{
|
|
||||||
union hv_x64_msr_hypercall_contents hypercall_msr;
|
|
||||||
|
|
||||||
/* Reset our OS id */
|
return 0;
|
||||||
wrmsrl(HV_X64_MSR_GUEST_OS_ID, 0);
|
|
||||||
|
|
||||||
if (hv_context.hypercall_page) {
|
|
||||||
hypercall_msr.as_uint64 = 0;
|
|
||||||
wrmsrl(HV_X64_MSR_HYPERCALL, hypercall_msr.as_uint64);
|
|
||||||
if (!crash)
|
|
||||||
vfree(hv_context.hypercall_page);
|
|
||||||
hv_context.hypercall_page = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef CONFIG_X86_64
|
|
||||||
/*
|
|
||||||
* Cleanup the TSC page based CS.
|
|
||||||
*/
|
|
||||||
if (ms_hyperv.features & HV_X64_MSR_REFERENCE_TSC_AVAILABLE) {
|
|
||||||
/*
|
|
||||||
* Crash can happen in an interrupt context and unregistering
|
|
||||||
* a clocksource is impossible and redundant in this case.
|
|
||||||
*/
|
|
||||||
if (!oops_in_progress) {
|
|
||||||
clocksource_change_rating(&hyperv_cs_tsc, 10);
|
|
||||||
clocksource_unregister(&hyperv_cs_tsc);
|
|
||||||
}
|
|
||||||
|
|
||||||
hypercall_msr.as_uint64 = 0;
|
|
||||||
wrmsrl(HV_X64_MSR_REFERENCE_TSC, hypercall_msr.as_uint64);
|
|
||||||
if (!crash)
|
|
||||||
vfree(hv_context.tsc_page);
|
|
||||||
hv_context.tsc_page = NULL;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -325,25 +68,24 @@ int hv_post_message(union hv_connection_id connection_id,
|
||||||
enum hv_message_type message_type,
|
enum hv_message_type message_type,
|
||||||
void *payload, size_t payload_size)
|
void *payload, size_t payload_size)
|
||||||
{
|
{
|
||||||
|
|
||||||
struct hv_input_post_message *aligned_msg;
|
struct hv_input_post_message *aligned_msg;
|
||||||
|
struct hv_per_cpu_context *hv_cpu;
|
||||||
u64 status;
|
u64 status;
|
||||||
|
|
||||||
if (payload_size > HV_MESSAGE_PAYLOAD_BYTE_COUNT)
|
if (payload_size > HV_MESSAGE_PAYLOAD_BYTE_COUNT)
|
||||||
return -EMSGSIZE;
|
return -EMSGSIZE;
|
||||||
|
|
||||||
aligned_msg = (struct hv_input_post_message *)
|
hv_cpu = get_cpu_ptr(hv_context.cpu_context);
|
||||||
hv_context.post_msg_page[get_cpu()];
|
aligned_msg = hv_cpu->post_msg_page;
|
||||||
|
|
||||||
aligned_msg->connectionid = connection_id;
|
aligned_msg->connectionid = connection_id;
|
||||||
aligned_msg->reserved = 0;
|
aligned_msg->reserved = 0;
|
||||||
aligned_msg->message_type = message_type;
|
aligned_msg->message_type = message_type;
|
||||||
aligned_msg->payload_size = payload_size;
|
aligned_msg->payload_size = payload_size;
|
||||||
memcpy((void *)aligned_msg->payload, payload, payload_size);
|
memcpy((void *)aligned_msg->payload, payload, payload_size);
|
||||||
|
put_cpu_ptr(hv_cpu);
|
||||||
|
|
||||||
status = hv_do_hypercall(HVCALL_POST_MESSAGE, aligned_msg, NULL);
|
status = hv_do_hypercall(HVCALL_POST_MESSAGE, aligned_msg, NULL);
|
||||||
|
|
||||||
put_cpu();
|
|
||||||
return status & 0xFFFF;
|
return status & 0xFFFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -354,16 +96,16 @@ static int hv_ce_set_next_event(unsigned long delta,
|
||||||
|
|
||||||
WARN_ON(!clockevent_state_oneshot(evt));
|
WARN_ON(!clockevent_state_oneshot(evt));
|
||||||
|
|
||||||
rdmsrl(HV_X64_MSR_TIME_REF_COUNT, current_tick);
|
hv_get_current_tick(current_tick);
|
||||||
current_tick += delta;
|
current_tick += delta;
|
||||||
wrmsrl(HV_X64_MSR_STIMER0_COUNT, current_tick);
|
hv_init_timer(HV_X64_MSR_STIMER0_COUNT, current_tick);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int hv_ce_shutdown(struct clock_event_device *evt)
|
static int hv_ce_shutdown(struct clock_event_device *evt)
|
||||||
{
|
{
|
||||||
wrmsrl(HV_X64_MSR_STIMER0_COUNT, 0);
|
hv_init_timer(HV_X64_MSR_STIMER0_COUNT, 0);
|
||||||
wrmsrl(HV_X64_MSR_STIMER0_CONFIG, 0);
|
hv_init_timer_config(HV_X64_MSR_STIMER0_CONFIG, 0);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -375,7 +117,7 @@ static int hv_ce_set_oneshot(struct clock_event_device *evt)
|
||||||
timer_cfg.enable = 1;
|
timer_cfg.enable = 1;
|
||||||
timer_cfg.auto_enable = 1;
|
timer_cfg.auto_enable = 1;
|
||||||
timer_cfg.sintx = VMBUS_MESSAGE_SINT;
|
timer_cfg.sintx = VMBUS_MESSAGE_SINT;
|
||||||
wrmsrl(HV_X64_MSR_STIMER0_CONFIG, timer_cfg.as_uint64);
|
hv_init_timer_config(HV_X64_MSR_STIMER0_CONFIG, timer_cfg.as_uint64);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -400,8 +142,6 @@ static void hv_init_clockevent_device(struct clock_event_device *dev, int cpu)
|
||||||
|
|
||||||
int hv_synic_alloc(void)
|
int hv_synic_alloc(void)
|
||||||
{
|
{
|
||||||
size_t size = sizeof(struct tasklet_struct);
|
|
||||||
size_t ced_size = sizeof(struct clock_event_device);
|
|
||||||
int cpu;
|
int cpu;
|
||||||
|
|
||||||
hv_context.hv_numa_map = kzalloc(sizeof(struct cpumask) * nr_node_ids,
|
hv_context.hv_numa_map = kzalloc(sizeof(struct cpumask) * nr_node_ids,
|
||||||
|
@ -411,52 +151,42 @@ int hv_synic_alloc(void)
|
||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
for_each_online_cpu(cpu) {
|
for_each_present_cpu(cpu) {
|
||||||
hv_context.event_dpc[cpu] = kmalloc(size, GFP_ATOMIC);
|
struct hv_per_cpu_context *hv_cpu
|
||||||
if (hv_context.event_dpc[cpu] == NULL) {
|
= per_cpu_ptr(hv_context.cpu_context, cpu);
|
||||||
pr_err("Unable to allocate event dpc\n");
|
|
||||||
goto err;
|
|
||||||
}
|
|
||||||
tasklet_init(hv_context.event_dpc[cpu], vmbus_on_event, cpu);
|
|
||||||
|
|
||||||
hv_context.msg_dpc[cpu] = kmalloc(size, GFP_ATOMIC);
|
memset(hv_cpu, 0, sizeof(*hv_cpu));
|
||||||
if (hv_context.msg_dpc[cpu] == NULL) {
|
tasklet_init(&hv_cpu->msg_dpc,
|
||||||
pr_err("Unable to allocate event dpc\n");
|
vmbus_on_msg_dpc, (unsigned long) hv_cpu);
|
||||||
goto err;
|
|
||||||
}
|
|
||||||
tasklet_init(hv_context.msg_dpc[cpu], vmbus_on_msg_dpc, cpu);
|
|
||||||
|
|
||||||
hv_context.clk_evt[cpu] = kzalloc(ced_size, GFP_ATOMIC);
|
hv_cpu->clk_evt = kzalloc(sizeof(struct clock_event_device),
|
||||||
if (hv_context.clk_evt[cpu] == NULL) {
|
GFP_KERNEL);
|
||||||
|
if (hv_cpu->clk_evt == NULL) {
|
||||||
pr_err("Unable to allocate clock event device\n");
|
pr_err("Unable to allocate clock event device\n");
|
||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
|
hv_init_clockevent_device(hv_cpu->clk_evt, cpu);
|
||||||
|
|
||||||
hv_init_clockevent_device(hv_context.clk_evt[cpu], cpu);
|
hv_cpu->synic_message_page =
|
||||||
|
|
||||||
hv_context.synic_message_page[cpu] =
|
|
||||||
(void *)get_zeroed_page(GFP_ATOMIC);
|
(void *)get_zeroed_page(GFP_ATOMIC);
|
||||||
|
if (hv_cpu->synic_message_page == NULL) {
|
||||||
if (hv_context.synic_message_page[cpu] == NULL) {
|
|
||||||
pr_err("Unable to allocate SYNIC message page\n");
|
pr_err("Unable to allocate SYNIC message page\n");
|
||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
hv_context.synic_event_page[cpu] =
|
hv_cpu->synic_event_page = (void *)get_zeroed_page(GFP_ATOMIC);
|
||||||
(void *)get_zeroed_page(GFP_ATOMIC);
|
if (hv_cpu->synic_event_page == NULL) {
|
||||||
|
|
||||||
if (hv_context.synic_event_page[cpu] == NULL) {
|
|
||||||
pr_err("Unable to allocate SYNIC event page\n");
|
pr_err("Unable to allocate SYNIC event page\n");
|
||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
hv_context.post_msg_page[cpu] =
|
hv_cpu->post_msg_page = (void *)get_zeroed_page(GFP_ATOMIC);
|
||||||
(void *)get_zeroed_page(GFP_ATOMIC);
|
if (hv_cpu->post_msg_page == NULL) {
|
||||||
|
|
||||||
if (hv_context.post_msg_page[cpu] == NULL) {
|
|
||||||
pr_err("Unable to allocate post msg page\n");
|
pr_err("Unable to allocate post msg page\n");
|
||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
INIT_LIST_HEAD(&hv_cpu->chan_list);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -464,26 +194,24 @@ int hv_synic_alloc(void)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void hv_synic_free_cpu(int cpu)
|
|
||||||
{
|
|
||||||
kfree(hv_context.event_dpc[cpu]);
|
|
||||||
kfree(hv_context.msg_dpc[cpu]);
|
|
||||||
kfree(hv_context.clk_evt[cpu]);
|
|
||||||
if (hv_context.synic_event_page[cpu])
|
|
||||||
free_page((unsigned long)hv_context.synic_event_page[cpu]);
|
|
||||||
if (hv_context.synic_message_page[cpu])
|
|
||||||
free_page((unsigned long)hv_context.synic_message_page[cpu]);
|
|
||||||
if (hv_context.post_msg_page[cpu])
|
|
||||||
free_page((unsigned long)hv_context.post_msg_page[cpu]);
|
|
||||||
}
|
|
||||||
|
|
||||||
void hv_synic_free(void)
|
void hv_synic_free(void)
|
||||||
{
|
{
|
||||||
int cpu;
|
int cpu;
|
||||||
|
|
||||||
|
for_each_present_cpu(cpu) {
|
||||||
|
struct hv_per_cpu_context *hv_cpu
|
||||||
|
= per_cpu_ptr(hv_context.cpu_context, cpu);
|
||||||
|
|
||||||
|
if (hv_cpu->synic_event_page)
|
||||||
|
free_page((unsigned long)hv_cpu->synic_event_page);
|
||||||
|
if (hv_cpu->synic_message_page)
|
||||||
|
free_page((unsigned long)hv_cpu->synic_message_page);
|
||||||
|
if (hv_cpu->post_msg_page)
|
||||||
|
free_page((unsigned long)hv_cpu->post_msg_page);
|
||||||
|
}
|
||||||
|
|
||||||
kfree(hv_context.hv_numa_map);
|
kfree(hv_context.hv_numa_map);
|
||||||
for_each_online_cpu(cpu)
|
|
||||||
hv_synic_free_cpu(cpu);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -493,54 +221,49 @@ void hv_synic_free(void)
|
||||||
* retrieve the initialized message and event pages. Otherwise, we create and
|
* retrieve the initialized message and event pages. Otherwise, we create and
|
||||||
* initialize the message and event pages.
|
* initialize the message and event pages.
|
||||||
*/
|
*/
|
||||||
void hv_synic_init(void *arg)
|
int hv_synic_init(unsigned int cpu)
|
||||||
{
|
{
|
||||||
u64 version;
|
struct hv_per_cpu_context *hv_cpu
|
||||||
|
= per_cpu_ptr(hv_context.cpu_context, cpu);
|
||||||
union hv_synic_simp simp;
|
union hv_synic_simp simp;
|
||||||
union hv_synic_siefp siefp;
|
union hv_synic_siefp siefp;
|
||||||
union hv_synic_sint shared_sint;
|
union hv_synic_sint shared_sint;
|
||||||
union hv_synic_scontrol sctrl;
|
union hv_synic_scontrol sctrl;
|
||||||
u64 vp_index;
|
u64 vp_index;
|
||||||
|
|
||||||
int cpu = smp_processor_id();
|
|
||||||
|
|
||||||
if (!hv_context.hypercall_page)
|
|
||||||
return;
|
|
||||||
|
|
||||||
/* Check the version */
|
|
||||||
rdmsrl(HV_X64_MSR_SVERSION, version);
|
|
||||||
|
|
||||||
/* Setup the Synic's message page */
|
/* Setup the Synic's message page */
|
||||||
rdmsrl(HV_X64_MSR_SIMP, simp.as_uint64);
|
hv_get_simp(simp.as_uint64);
|
||||||
simp.simp_enabled = 1;
|
simp.simp_enabled = 1;
|
||||||
simp.base_simp_gpa = virt_to_phys(hv_context.synic_message_page[cpu])
|
simp.base_simp_gpa = virt_to_phys(hv_cpu->synic_message_page)
|
||||||
>> PAGE_SHIFT;
|
>> PAGE_SHIFT;
|
||||||
|
|
||||||
wrmsrl(HV_X64_MSR_SIMP, simp.as_uint64);
|
hv_set_simp(simp.as_uint64);
|
||||||
|
|
||||||
/* Setup the Synic's event page */
|
/* Setup the Synic's event page */
|
||||||
rdmsrl(HV_X64_MSR_SIEFP, siefp.as_uint64);
|
hv_get_siefp(siefp.as_uint64);
|
||||||
siefp.siefp_enabled = 1;
|
siefp.siefp_enabled = 1;
|
||||||
siefp.base_siefp_gpa = virt_to_phys(hv_context.synic_event_page[cpu])
|
siefp.base_siefp_gpa = virt_to_phys(hv_cpu->synic_event_page)
|
||||||
>> PAGE_SHIFT;
|
>> PAGE_SHIFT;
|
||||||
|
|
||||||
wrmsrl(HV_X64_MSR_SIEFP, siefp.as_uint64);
|
hv_set_siefp(siefp.as_uint64);
|
||||||
|
|
||||||
/* Setup the shared SINT. */
|
/* Setup the shared SINT. */
|
||||||
rdmsrl(HV_X64_MSR_SINT0 + VMBUS_MESSAGE_SINT, shared_sint.as_uint64);
|
hv_get_synint_state(HV_X64_MSR_SINT0 + VMBUS_MESSAGE_SINT,
|
||||||
|
shared_sint.as_uint64);
|
||||||
|
|
||||||
shared_sint.as_uint64 = 0;
|
shared_sint.as_uint64 = 0;
|
||||||
shared_sint.vector = HYPERVISOR_CALLBACK_VECTOR;
|
shared_sint.vector = HYPERVISOR_CALLBACK_VECTOR;
|
||||||
shared_sint.masked = false;
|
shared_sint.masked = false;
|
||||||
shared_sint.auto_eoi = true;
|
shared_sint.auto_eoi = true;
|
||||||
|
|
||||||
wrmsrl(HV_X64_MSR_SINT0 + VMBUS_MESSAGE_SINT, shared_sint.as_uint64);
|
hv_set_synint_state(HV_X64_MSR_SINT0 + VMBUS_MESSAGE_SINT,
|
||||||
|
shared_sint.as_uint64);
|
||||||
|
|
||||||
/* Enable the global synic bit */
|
/* Enable the global synic bit */
|
||||||
rdmsrl(HV_X64_MSR_SCONTROL, sctrl.as_uint64);
|
hv_get_synic_state(sctrl.as_uint64);
|
||||||
sctrl.enable = 1;
|
sctrl.enable = 1;
|
||||||
|
|
||||||
wrmsrl(HV_X64_MSR_SCONTROL, sctrl.as_uint64);
|
hv_set_synic_state(sctrl.as_uint64);
|
||||||
|
|
||||||
hv_context.synic_initialized = true;
|
hv_context.synic_initialized = true;
|
||||||
|
|
||||||
|
@ -549,20 +272,18 @@ void hv_synic_init(void *arg)
|
||||||
* of cpuid and Linux' notion of cpuid.
|
* of cpuid and Linux' notion of cpuid.
|
||||||
* This array will be indexed using Linux cpuid.
|
* This array will be indexed using Linux cpuid.
|
||||||
*/
|
*/
|
||||||
rdmsrl(HV_X64_MSR_VP_INDEX, vp_index);
|
hv_get_vp_index(vp_index);
|
||||||
hv_context.vp_index[cpu] = (u32)vp_index;
|
hv_context.vp_index[cpu] = (u32)vp_index;
|
||||||
|
|
||||||
INIT_LIST_HEAD(&hv_context.percpu_list[cpu]);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Register the per-cpu clockevent source.
|
* Register the per-cpu clockevent source.
|
||||||
*/
|
*/
|
||||||
if (ms_hyperv.features & HV_X64_MSR_SYNTIMER_AVAILABLE)
|
if (ms_hyperv.features & HV_X64_MSR_SYNTIMER_AVAILABLE)
|
||||||
clockevents_config_and_register(hv_context.clk_evt[cpu],
|
clockevents_config_and_register(hv_cpu->clk_evt,
|
||||||
HV_TIMER_FREQUENCY,
|
HV_TIMER_FREQUENCY,
|
||||||
HV_MIN_DELTA_TICKS,
|
HV_MIN_DELTA_TICKS,
|
||||||
HV_MAX_MAX_DELTA_TICKS);
|
HV_MAX_MAX_DELTA_TICKS);
|
||||||
return;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -575,52 +296,94 @@ void hv_synic_clockevents_cleanup(void)
|
||||||
if (!(ms_hyperv.features & HV_X64_MSR_SYNTIMER_AVAILABLE))
|
if (!(ms_hyperv.features & HV_X64_MSR_SYNTIMER_AVAILABLE))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
for_each_present_cpu(cpu)
|
for_each_present_cpu(cpu) {
|
||||||
clockevents_unbind_device(hv_context.clk_evt[cpu], cpu);
|
struct hv_per_cpu_context *hv_cpu
|
||||||
|
= per_cpu_ptr(hv_context.cpu_context, cpu);
|
||||||
|
|
||||||
|
clockevents_unbind_device(hv_cpu->clk_evt, cpu);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* hv_synic_cleanup - Cleanup routine for hv_synic_init().
|
* hv_synic_cleanup - Cleanup routine for hv_synic_init().
|
||||||
*/
|
*/
|
||||||
void hv_synic_cleanup(void *arg)
|
int hv_synic_cleanup(unsigned int cpu)
|
||||||
{
|
{
|
||||||
union hv_synic_sint shared_sint;
|
union hv_synic_sint shared_sint;
|
||||||
union hv_synic_simp simp;
|
union hv_synic_simp simp;
|
||||||
union hv_synic_siefp siefp;
|
union hv_synic_siefp siefp;
|
||||||
union hv_synic_scontrol sctrl;
|
union hv_synic_scontrol sctrl;
|
||||||
int cpu = smp_processor_id();
|
struct vmbus_channel *channel, *sc;
|
||||||
|
bool channel_found = false;
|
||||||
|
unsigned long flags;
|
||||||
|
|
||||||
if (!hv_context.synic_initialized)
|
if (!hv_context.synic_initialized)
|
||||||
return;
|
return -EFAULT;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Search for channels which are bound to the CPU we're about to
|
||||||
|
* cleanup. In case we find one and vmbus is still connected we need to
|
||||||
|
* fail, this will effectively prevent CPU offlining. There is no way
|
||||||
|
* we can re-bind channels to different CPUs for now.
|
||||||
|
*/
|
||||||
|
mutex_lock(&vmbus_connection.channel_mutex);
|
||||||
|
list_for_each_entry(channel, &vmbus_connection.chn_list, listentry) {
|
||||||
|
if (channel->target_cpu == cpu) {
|
||||||
|
channel_found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
spin_lock_irqsave(&channel->lock, flags);
|
||||||
|
list_for_each_entry(sc, &channel->sc_list, sc_list) {
|
||||||
|
if (sc->target_cpu == cpu) {
|
||||||
|
channel_found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
spin_unlock_irqrestore(&channel->lock, flags);
|
||||||
|
if (channel_found)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
mutex_unlock(&vmbus_connection.channel_mutex);
|
||||||
|
|
||||||
|
if (channel_found && vmbus_connection.conn_state == CONNECTED)
|
||||||
|
return -EBUSY;
|
||||||
|
|
||||||
/* Turn off clockevent device */
|
/* Turn off clockevent device */
|
||||||
if (ms_hyperv.features & HV_X64_MSR_SYNTIMER_AVAILABLE) {
|
if (ms_hyperv.features & HV_X64_MSR_SYNTIMER_AVAILABLE) {
|
||||||
clockevents_unbind_device(hv_context.clk_evt[cpu], cpu);
|
struct hv_per_cpu_context *hv_cpu
|
||||||
hv_ce_shutdown(hv_context.clk_evt[cpu]);
|
= this_cpu_ptr(hv_context.cpu_context);
|
||||||
|
|
||||||
|
clockevents_unbind_device(hv_cpu->clk_evt, cpu);
|
||||||
|
hv_ce_shutdown(hv_cpu->clk_evt);
|
||||||
|
put_cpu_ptr(hv_cpu);
|
||||||
}
|
}
|
||||||
|
|
||||||
rdmsrl(HV_X64_MSR_SINT0 + VMBUS_MESSAGE_SINT, shared_sint.as_uint64);
|
hv_get_synint_state(HV_X64_MSR_SINT0 + VMBUS_MESSAGE_SINT,
|
||||||
|
shared_sint.as_uint64);
|
||||||
|
|
||||||
shared_sint.masked = 1;
|
shared_sint.masked = 1;
|
||||||
|
|
||||||
/* Need to correctly cleanup in the case of SMP!!! */
|
/* Need to correctly cleanup in the case of SMP!!! */
|
||||||
/* Disable the interrupt */
|
/* Disable the interrupt */
|
||||||
wrmsrl(HV_X64_MSR_SINT0 + VMBUS_MESSAGE_SINT, shared_sint.as_uint64);
|
hv_set_synint_state(HV_X64_MSR_SINT0 + VMBUS_MESSAGE_SINT,
|
||||||
|
shared_sint.as_uint64);
|
||||||
|
|
||||||
rdmsrl(HV_X64_MSR_SIMP, simp.as_uint64);
|
hv_get_simp(simp.as_uint64);
|
||||||
simp.simp_enabled = 0;
|
simp.simp_enabled = 0;
|
||||||
simp.base_simp_gpa = 0;
|
simp.base_simp_gpa = 0;
|
||||||
|
|
||||||
wrmsrl(HV_X64_MSR_SIMP, simp.as_uint64);
|
hv_set_simp(simp.as_uint64);
|
||||||
|
|
||||||
rdmsrl(HV_X64_MSR_SIEFP, siefp.as_uint64);
|
hv_get_siefp(siefp.as_uint64);
|
||||||
siefp.siefp_enabled = 0;
|
siefp.siefp_enabled = 0;
|
||||||
siefp.base_siefp_gpa = 0;
|
siefp.base_siefp_gpa = 0;
|
||||||
|
|
||||||
wrmsrl(HV_X64_MSR_SIEFP, siefp.as_uint64);
|
hv_set_siefp(siefp.as_uint64);
|
||||||
|
|
||||||
/* Disable the global synic bit */
|
/* Disable the global synic bit */
|
||||||
rdmsrl(HV_X64_MSR_SCONTROL, sctrl.as_uint64);
|
hv_get_synic_state(sctrl.as_uint64);
|
||||||
sctrl.enable = 0;
|
sctrl.enable = 0;
|
||||||
wrmsrl(HV_X64_MSR_SCONTROL, sctrl.as_uint64);
|
hv_set_synic_state(sctrl.as_uint64);
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -587,6 +587,7 @@ static int hv_memory_notifier(struct notifier_block *nb, unsigned long val,
|
||||||
spin_lock_irqsave(&dm_device.ha_lock, flags);
|
spin_lock_irqsave(&dm_device.ha_lock, flags);
|
||||||
dm_device.num_pages_onlined += mem->nr_pages;
|
dm_device.num_pages_onlined += mem->nr_pages;
|
||||||
spin_unlock_irqrestore(&dm_device.ha_lock, flags);
|
spin_unlock_irqrestore(&dm_device.ha_lock, flags);
|
||||||
|
/* Fall through */
|
||||||
case MEM_CANCEL_ONLINE:
|
case MEM_CANCEL_ONLINE:
|
||||||
if (dm_device.ha_waiting) {
|
if (dm_device.ha_waiting) {
|
||||||
dm_device.ha_waiting = false;
|
dm_device.ha_waiting = false;
|
||||||
|
|
|
@ -31,6 +31,16 @@
|
||||||
#define WIN8_SRV_MINOR 1
|
#define WIN8_SRV_MINOR 1
|
||||||
#define WIN8_SRV_VERSION (WIN8_SRV_MAJOR << 16 | WIN8_SRV_MINOR)
|
#define WIN8_SRV_VERSION (WIN8_SRV_MAJOR << 16 | WIN8_SRV_MINOR)
|
||||||
|
|
||||||
|
#define FCOPY_VER_COUNT 1
|
||||||
|
static const int fcopy_versions[] = {
|
||||||
|
WIN8_SRV_VERSION
|
||||||
|
};
|
||||||
|
|
||||||
|
#define FW_VER_COUNT 1
|
||||||
|
static const int fw_versions[] = {
|
||||||
|
UTIL_FW_VERSION
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Global state maintained for transaction that is being processed.
|
* Global state maintained for transaction that is being processed.
|
||||||
* For a class of integration services, including the "file copy service",
|
* For a class of integration services, including the "file copy service",
|
||||||
|
@ -61,6 +71,7 @@ static DECLARE_WORK(fcopy_send_work, fcopy_send_data);
|
||||||
static const char fcopy_devname[] = "vmbus/hv_fcopy";
|
static const char fcopy_devname[] = "vmbus/hv_fcopy";
|
||||||
static u8 *recv_buffer;
|
static u8 *recv_buffer;
|
||||||
static struct hvutil_transport *hvt;
|
static struct hvutil_transport *hvt;
|
||||||
|
static struct completion release_event;
|
||||||
/*
|
/*
|
||||||
* This state maintains the version number registered by the daemon.
|
* This state maintains the version number registered by the daemon.
|
||||||
*/
|
*/
|
||||||
|
@ -227,8 +238,6 @@ void hv_fcopy_onchannelcallback(void *context)
|
||||||
u64 requestid;
|
u64 requestid;
|
||||||
struct hv_fcopy_hdr *fcopy_msg;
|
struct hv_fcopy_hdr *fcopy_msg;
|
||||||
struct icmsg_hdr *icmsghdr;
|
struct icmsg_hdr *icmsghdr;
|
||||||
struct icmsg_negotiate *negop = NULL;
|
|
||||||
int util_fw_version;
|
|
||||||
int fcopy_srv_version;
|
int fcopy_srv_version;
|
||||||
|
|
||||||
if (fcopy_transaction.state > HVUTIL_READY)
|
if (fcopy_transaction.state > HVUTIL_READY)
|
||||||
|
@ -242,10 +251,15 @@ void hv_fcopy_onchannelcallback(void *context)
|
||||||
icmsghdr = (struct icmsg_hdr *)&recv_buffer[
|
icmsghdr = (struct icmsg_hdr *)&recv_buffer[
|
||||||
sizeof(struct vmbuspipe_hdr)];
|
sizeof(struct vmbuspipe_hdr)];
|
||||||
if (icmsghdr->icmsgtype == ICMSGTYPE_NEGOTIATE) {
|
if (icmsghdr->icmsgtype == ICMSGTYPE_NEGOTIATE) {
|
||||||
util_fw_version = UTIL_FW_VERSION;
|
if (vmbus_prep_negotiate_resp(icmsghdr, recv_buffer,
|
||||||
fcopy_srv_version = WIN8_SRV_VERSION;
|
fw_versions, FW_VER_COUNT,
|
||||||
vmbus_prep_negotiate_resp(icmsghdr, negop, recv_buffer,
|
fcopy_versions, FCOPY_VER_COUNT,
|
||||||
util_fw_version, fcopy_srv_version);
|
NULL, &fcopy_srv_version)) {
|
||||||
|
|
||||||
|
pr_info("FCopy IC version %d.%d\n",
|
||||||
|
fcopy_srv_version >> 16,
|
||||||
|
fcopy_srv_version & 0xFFFF);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
fcopy_msg = (struct hv_fcopy_hdr *)&recv_buffer[
|
fcopy_msg = (struct hv_fcopy_hdr *)&recv_buffer[
|
||||||
sizeof(struct vmbuspipe_hdr) +
|
sizeof(struct vmbuspipe_hdr) +
|
||||||
|
@ -317,6 +331,7 @@ static void fcopy_on_reset(void)
|
||||||
|
|
||||||
if (cancel_delayed_work_sync(&fcopy_timeout_work))
|
if (cancel_delayed_work_sync(&fcopy_timeout_work))
|
||||||
fcopy_respond_to_host(HV_E_FAIL);
|
fcopy_respond_to_host(HV_E_FAIL);
|
||||||
|
complete(&release_event);
|
||||||
}
|
}
|
||||||
|
|
||||||
int hv_fcopy_init(struct hv_util_service *srv)
|
int hv_fcopy_init(struct hv_util_service *srv)
|
||||||
|
@ -324,6 +339,7 @@ int hv_fcopy_init(struct hv_util_service *srv)
|
||||||
recv_buffer = srv->recv_buffer;
|
recv_buffer = srv->recv_buffer;
|
||||||
fcopy_transaction.recv_channel = srv->channel;
|
fcopy_transaction.recv_channel = srv->channel;
|
||||||
|
|
||||||
|
init_completion(&release_event);
|
||||||
/*
|
/*
|
||||||
* When this driver loads, the user level daemon that
|
* When this driver loads, the user level daemon that
|
||||||
* processes the host requests may not yet be running.
|
* processes the host requests may not yet be running.
|
||||||
|
@ -345,4 +361,5 @@ void hv_fcopy_deinit(void)
|
||||||
fcopy_transaction.state = HVUTIL_DEVICE_DYING;
|
fcopy_transaction.state = HVUTIL_DEVICE_DYING;
|
||||||
cancel_delayed_work_sync(&fcopy_timeout_work);
|
cancel_delayed_work_sync(&fcopy_timeout_work);
|
||||||
hvutil_transport_destroy(hvt);
|
hvutil_transport_destroy(hvt);
|
||||||
|
wait_for_completion(&release_event);
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,19 @@
|
||||||
#define WIN8_SRV_MINOR 0
|
#define WIN8_SRV_MINOR 0
|
||||||
#define WIN8_SRV_VERSION (WIN8_SRV_MAJOR << 16 | WIN8_SRV_MINOR)
|
#define WIN8_SRV_VERSION (WIN8_SRV_MAJOR << 16 | WIN8_SRV_MINOR)
|
||||||
|
|
||||||
|
#define KVP_VER_COUNT 3
|
||||||
|
static const int kvp_versions[] = {
|
||||||
|
WIN8_SRV_VERSION,
|
||||||
|
WIN7_SRV_VERSION,
|
||||||
|
WS2008_SRV_VERSION
|
||||||
|
};
|
||||||
|
|
||||||
|
#define FW_VER_COUNT 2
|
||||||
|
static const int fw_versions[] = {
|
||||||
|
UTIL_FW_VERSION,
|
||||||
|
UTIL_WS2K8_FW_VERSION
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Global state maintained for transaction that is being processed. For a class
|
* Global state maintained for transaction that is being processed. For a class
|
||||||
* of integration services, including the "KVP service", the specified protocol
|
* of integration services, including the "KVP service", the specified protocol
|
||||||
|
@ -88,6 +101,7 @@ static DECLARE_WORK(kvp_sendkey_work, kvp_send_key);
|
||||||
static const char kvp_devname[] = "vmbus/hv_kvp";
|
static const char kvp_devname[] = "vmbus/hv_kvp";
|
||||||
static u8 *recv_buffer;
|
static u8 *recv_buffer;
|
||||||
static struct hvutil_transport *hvt;
|
static struct hvutil_transport *hvt;
|
||||||
|
static struct completion release_event;
|
||||||
/*
|
/*
|
||||||
* Register the kernel component with the user-level daemon.
|
* Register the kernel component with the user-level daemon.
|
||||||
* As part of this registration, pass the LIC version number.
|
* As part of this registration, pass the LIC version number.
|
||||||
|
@ -609,8 +623,6 @@ void hv_kvp_onchannelcallback(void *context)
|
||||||
struct hv_kvp_msg *kvp_msg;
|
struct hv_kvp_msg *kvp_msg;
|
||||||
|
|
||||||
struct icmsg_hdr *icmsghdrp;
|
struct icmsg_hdr *icmsghdrp;
|
||||||
struct icmsg_negotiate *negop = NULL;
|
|
||||||
int util_fw_version;
|
|
||||||
int kvp_srv_version;
|
int kvp_srv_version;
|
||||||
static enum {NEGO_NOT_STARTED,
|
static enum {NEGO_NOT_STARTED,
|
||||||
NEGO_IN_PROGRESS,
|
NEGO_IN_PROGRESS,
|
||||||
|
@ -639,28 +651,14 @@ void hv_kvp_onchannelcallback(void *context)
|
||||||
sizeof(struct vmbuspipe_hdr)];
|
sizeof(struct vmbuspipe_hdr)];
|
||||||
|
|
||||||
if (icmsghdrp->icmsgtype == ICMSGTYPE_NEGOTIATE) {
|
if (icmsghdrp->icmsgtype == ICMSGTYPE_NEGOTIATE) {
|
||||||
/*
|
if (vmbus_prep_negotiate_resp(icmsghdrp,
|
||||||
* Based on the host, select appropriate
|
recv_buffer, fw_versions, FW_VER_COUNT,
|
||||||
* framework and service versions we will
|
kvp_versions, KVP_VER_COUNT,
|
||||||
* negotiate.
|
NULL, &kvp_srv_version)) {
|
||||||
*/
|
pr_info("KVP IC version %d.%d\n",
|
||||||
switch (vmbus_proto_version) {
|
kvp_srv_version >> 16,
|
||||||
case (VERSION_WS2008):
|
kvp_srv_version & 0xFFFF);
|
||||||
util_fw_version = UTIL_WS2K8_FW_VERSION;
|
|
||||||
kvp_srv_version = WS2008_SRV_VERSION;
|
|
||||||
break;
|
|
||||||
case (VERSION_WIN7):
|
|
||||||
util_fw_version = UTIL_FW_VERSION;
|
|
||||||
kvp_srv_version = WIN7_SRV_VERSION;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
util_fw_version = UTIL_FW_VERSION;
|
|
||||||
kvp_srv_version = WIN8_SRV_VERSION;
|
|
||||||
}
|
}
|
||||||
vmbus_prep_negotiate_resp(icmsghdrp, negop,
|
|
||||||
recv_buffer, util_fw_version,
|
|
||||||
kvp_srv_version);
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
kvp_msg = (struct hv_kvp_msg *)&recv_buffer[
|
kvp_msg = (struct hv_kvp_msg *)&recv_buffer[
|
||||||
sizeof(struct vmbuspipe_hdr) +
|
sizeof(struct vmbuspipe_hdr) +
|
||||||
|
@ -716,6 +714,7 @@ static void kvp_on_reset(void)
|
||||||
if (cancel_delayed_work_sync(&kvp_timeout_work))
|
if (cancel_delayed_work_sync(&kvp_timeout_work))
|
||||||
kvp_respond_to_host(NULL, HV_E_FAIL);
|
kvp_respond_to_host(NULL, HV_E_FAIL);
|
||||||
kvp_transaction.state = HVUTIL_DEVICE_INIT;
|
kvp_transaction.state = HVUTIL_DEVICE_INIT;
|
||||||
|
complete(&release_event);
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
|
@ -724,6 +723,7 @@ hv_kvp_init(struct hv_util_service *srv)
|
||||||
recv_buffer = srv->recv_buffer;
|
recv_buffer = srv->recv_buffer;
|
||||||
kvp_transaction.recv_channel = srv->channel;
|
kvp_transaction.recv_channel = srv->channel;
|
||||||
|
|
||||||
|
init_completion(&release_event);
|
||||||
/*
|
/*
|
||||||
* When this driver loads, the user level daemon that
|
* When this driver loads, the user level daemon that
|
||||||
* processes the host requests may not yet be running.
|
* processes the host requests may not yet be running.
|
||||||
|
@ -747,4 +747,5 @@ void hv_kvp_deinit(void)
|
||||||
cancel_delayed_work_sync(&kvp_timeout_work);
|
cancel_delayed_work_sync(&kvp_timeout_work);
|
||||||
cancel_work_sync(&kvp_sendkey_work);
|
cancel_work_sync(&kvp_sendkey_work);
|
||||||
hvutil_transport_destroy(hvt);
|
hvutil_transport_destroy(hvt);
|
||||||
|
wait_for_completion(&release_event);
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,16 @@
|
||||||
#define VSS_MINOR 0
|
#define VSS_MINOR 0
|
||||||
#define VSS_VERSION (VSS_MAJOR << 16 | VSS_MINOR)
|
#define VSS_VERSION (VSS_MAJOR << 16 | VSS_MINOR)
|
||||||
|
|
||||||
|
#define VSS_VER_COUNT 1
|
||||||
|
static const int vss_versions[] = {
|
||||||
|
VSS_VERSION
|
||||||
|
};
|
||||||
|
|
||||||
|
#define FW_VER_COUNT 1
|
||||||
|
static const int fw_versions[] = {
|
||||||
|
UTIL_FW_VERSION
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Timeout values are based on expecations from host
|
* Timeout values are based on expecations from host
|
||||||
*/
|
*/
|
||||||
|
@ -69,6 +79,7 @@ static int dm_reg_value;
|
||||||
static const char vss_devname[] = "vmbus/hv_vss";
|
static const char vss_devname[] = "vmbus/hv_vss";
|
||||||
static __u8 *recv_buffer;
|
static __u8 *recv_buffer;
|
||||||
static struct hvutil_transport *hvt;
|
static struct hvutil_transport *hvt;
|
||||||
|
static struct completion release_event;
|
||||||
|
|
||||||
static void vss_timeout_func(struct work_struct *dummy);
|
static void vss_timeout_func(struct work_struct *dummy);
|
||||||
static void vss_handle_request(struct work_struct *dummy);
|
static void vss_handle_request(struct work_struct *dummy);
|
||||||
|
@ -293,10 +304,9 @@ void hv_vss_onchannelcallback(void *context)
|
||||||
u32 recvlen;
|
u32 recvlen;
|
||||||
u64 requestid;
|
u64 requestid;
|
||||||
struct hv_vss_msg *vss_msg;
|
struct hv_vss_msg *vss_msg;
|
||||||
|
int vss_srv_version;
|
||||||
|
|
||||||
struct icmsg_hdr *icmsghdrp;
|
struct icmsg_hdr *icmsghdrp;
|
||||||
struct icmsg_negotiate *negop = NULL;
|
|
||||||
|
|
||||||
if (vss_transaction.state > HVUTIL_READY)
|
if (vss_transaction.state > HVUTIL_READY)
|
||||||
return;
|
return;
|
||||||
|
@ -309,9 +319,15 @@ void hv_vss_onchannelcallback(void *context)
|
||||||
sizeof(struct vmbuspipe_hdr)];
|
sizeof(struct vmbuspipe_hdr)];
|
||||||
|
|
||||||
if (icmsghdrp->icmsgtype == ICMSGTYPE_NEGOTIATE) {
|
if (icmsghdrp->icmsgtype == ICMSGTYPE_NEGOTIATE) {
|
||||||
vmbus_prep_negotiate_resp(icmsghdrp, negop,
|
if (vmbus_prep_negotiate_resp(icmsghdrp,
|
||||||
recv_buffer, UTIL_FW_VERSION,
|
recv_buffer, fw_versions, FW_VER_COUNT,
|
||||||
VSS_VERSION);
|
vss_versions, VSS_VER_COUNT,
|
||||||
|
NULL, &vss_srv_version)) {
|
||||||
|
|
||||||
|
pr_info("VSS IC version %d.%d\n",
|
||||||
|
vss_srv_version >> 16,
|
||||||
|
vss_srv_version & 0xFFFF);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
vss_msg = (struct hv_vss_msg *)&recv_buffer[
|
vss_msg = (struct hv_vss_msg *)&recv_buffer[
|
||||||
sizeof(struct vmbuspipe_hdr) +
|
sizeof(struct vmbuspipe_hdr) +
|
||||||
|
@ -345,11 +361,13 @@ static void vss_on_reset(void)
|
||||||
if (cancel_delayed_work_sync(&vss_timeout_work))
|
if (cancel_delayed_work_sync(&vss_timeout_work))
|
||||||
vss_respond_to_host(HV_E_FAIL);
|
vss_respond_to_host(HV_E_FAIL);
|
||||||
vss_transaction.state = HVUTIL_DEVICE_INIT;
|
vss_transaction.state = HVUTIL_DEVICE_INIT;
|
||||||
|
complete(&release_event);
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
hv_vss_init(struct hv_util_service *srv)
|
hv_vss_init(struct hv_util_service *srv)
|
||||||
{
|
{
|
||||||
|
init_completion(&release_event);
|
||||||
if (vmbus_proto_version < VERSION_WIN8_1) {
|
if (vmbus_proto_version < VERSION_WIN8_1) {
|
||||||
pr_warn("Integration service 'Backup (volume snapshot)'"
|
pr_warn("Integration service 'Backup (volume snapshot)'"
|
||||||
" not supported on this host version.\n");
|
" not supported on this host version.\n");
|
||||||
|
@ -382,4 +400,5 @@ void hv_vss_deinit(void)
|
||||||
cancel_delayed_work_sync(&vss_timeout_work);
|
cancel_delayed_work_sync(&vss_timeout_work);
|
||||||
cancel_work_sync(&vss_handle_request_work);
|
cancel_work_sync(&vss_handle_request_work);
|
||||||
hvutil_transport_destroy(hvt);
|
hvutil_transport_destroy(hvt);
|
||||||
|
wait_for_completion(&release_event);
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,9 @@
|
||||||
#include <linux/sysctl.h>
|
#include <linux/sysctl.h>
|
||||||
#include <linux/reboot.h>
|
#include <linux/reboot.h>
|
||||||
#include <linux/hyperv.h>
|
#include <linux/hyperv.h>
|
||||||
|
#include <linux/clockchips.h>
|
||||||
|
#include <linux/ptp_clock_kernel.h>
|
||||||
|
#include <asm/mshyperv.h>
|
||||||
|
|
||||||
#include "hyperv_vmbus.h"
|
#include "hyperv_vmbus.h"
|
||||||
|
|
||||||
|
@ -57,7 +60,31 @@
|
||||||
static int sd_srv_version;
|
static int sd_srv_version;
|
||||||
static int ts_srv_version;
|
static int ts_srv_version;
|
||||||
static int hb_srv_version;
|
static int hb_srv_version;
|
||||||
static int util_fw_version;
|
|
||||||
|
#define SD_VER_COUNT 2
|
||||||
|
static const int sd_versions[] = {
|
||||||
|
SD_VERSION,
|
||||||
|
SD_VERSION_1
|
||||||
|
};
|
||||||
|
|
||||||
|
#define TS_VER_COUNT 3
|
||||||
|
static const int ts_versions[] = {
|
||||||
|
TS_VERSION,
|
||||||
|
TS_VERSION_3,
|
||||||
|
TS_VERSION_1
|
||||||
|
};
|
||||||
|
|
||||||
|
#define HB_VER_COUNT 2
|
||||||
|
static const int hb_versions[] = {
|
||||||
|
HB_VERSION,
|
||||||
|
HB_VERSION_1
|
||||||
|
};
|
||||||
|
|
||||||
|
#define FW_VER_COUNT 2
|
||||||
|
static const int fw_versions[] = {
|
||||||
|
UTIL_FW_VERSION,
|
||||||
|
UTIL_WS2K8_FW_VERSION
|
||||||
|
};
|
||||||
|
|
||||||
static void shutdown_onchannelcallback(void *context);
|
static void shutdown_onchannelcallback(void *context);
|
||||||
static struct hv_util_service util_shutdown = {
|
static struct hv_util_service util_shutdown = {
|
||||||
|
@ -118,7 +145,6 @@ static void shutdown_onchannelcallback(void *context)
|
||||||
struct shutdown_msg_data *shutdown_msg;
|
struct shutdown_msg_data *shutdown_msg;
|
||||||
|
|
||||||
struct icmsg_hdr *icmsghdrp;
|
struct icmsg_hdr *icmsghdrp;
|
||||||
struct icmsg_negotiate *negop = NULL;
|
|
||||||
|
|
||||||
vmbus_recvpacket(channel, shut_txf_buf,
|
vmbus_recvpacket(channel, shut_txf_buf,
|
||||||
PAGE_SIZE, &recvlen, &requestid);
|
PAGE_SIZE, &recvlen, &requestid);
|
||||||
|
@ -128,9 +154,14 @@ static void shutdown_onchannelcallback(void *context)
|
||||||
sizeof(struct vmbuspipe_hdr)];
|
sizeof(struct vmbuspipe_hdr)];
|
||||||
|
|
||||||
if (icmsghdrp->icmsgtype == ICMSGTYPE_NEGOTIATE) {
|
if (icmsghdrp->icmsgtype == ICMSGTYPE_NEGOTIATE) {
|
||||||
vmbus_prep_negotiate_resp(icmsghdrp, negop,
|
if (vmbus_prep_negotiate_resp(icmsghdrp, shut_txf_buf,
|
||||||
shut_txf_buf, util_fw_version,
|
fw_versions, FW_VER_COUNT,
|
||||||
sd_srv_version);
|
sd_versions, SD_VER_COUNT,
|
||||||
|
NULL, &sd_srv_version)) {
|
||||||
|
pr_info("Shutdown IC version %d.%d\n",
|
||||||
|
sd_srv_version >> 16,
|
||||||
|
sd_srv_version & 0xFFFF);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
shutdown_msg =
|
shutdown_msg =
|
||||||
(struct shutdown_msg_data *)&shut_txf_buf[
|
(struct shutdown_msg_data *)&shut_txf_buf[
|
||||||
|
@ -182,30 +213,16 @@ struct adj_time_work {
|
||||||
static void hv_set_host_time(struct work_struct *work)
|
static void hv_set_host_time(struct work_struct *work)
|
||||||
{
|
{
|
||||||
struct adj_time_work *wrk;
|
struct adj_time_work *wrk;
|
||||||
s64 host_tns;
|
struct timespec64 host_ts;
|
||||||
u64 newtime;
|
u64 reftime, newtime;
|
||||||
struct timespec host_ts;
|
|
||||||
|
|
||||||
wrk = container_of(work, struct adj_time_work, work);
|
wrk = container_of(work, struct adj_time_work, work);
|
||||||
|
|
||||||
newtime = wrk->host_time;
|
reftime = hyperv_cs->read(hyperv_cs);
|
||||||
if (ts_srv_version > TS_VERSION_3) {
|
newtime = wrk->host_time + (reftime - wrk->ref_time);
|
||||||
/*
|
host_ts = ns_to_timespec64((newtime - WLTIMEDELTA) * 100);
|
||||||
* Some latency has been introduced since Hyper-V generated
|
|
||||||
* its time sample. Take that latency into account before
|
|
||||||
* using TSC reference time sample from Hyper-V.
|
|
||||||
*
|
|
||||||
* This sample is given by TimeSync v4 and above hosts.
|
|
||||||
*/
|
|
||||||
u64 current_tick;
|
|
||||||
|
|
||||||
rdmsrl(HV_X64_MSR_TIME_REF_COUNT, current_tick);
|
do_settimeofday64(&host_ts);
|
||||||
newtime += (current_tick - wrk->ref_time);
|
|
||||||
}
|
|
||||||
host_tns = (newtime - WLTIMEDELTA) * 100;
|
|
||||||
host_ts = ns_to_timespec(host_tns);
|
|
||||||
|
|
||||||
do_settimeofday(&host_ts);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -222,22 +239,60 @@ static void hv_set_host_time(struct work_struct *work)
|
||||||
* to discipline the clock.
|
* to discipline the clock.
|
||||||
*/
|
*/
|
||||||
static struct adj_time_work wrk;
|
static struct adj_time_work wrk;
|
||||||
static inline void adj_guesttime(u64 hosttime, u64 reftime, u8 flags)
|
|
||||||
|
/*
|
||||||
|
* The last time sample, received from the host. PTP device responds to
|
||||||
|
* requests by using this data and the current partition-wide time reference
|
||||||
|
* count.
|
||||||
|
*/
|
||||||
|
static struct {
|
||||||
|
u64 host_time;
|
||||||
|
u64 ref_time;
|
||||||
|
struct system_time_snapshot snap;
|
||||||
|
spinlock_t lock;
|
||||||
|
} host_ts;
|
||||||
|
|
||||||
|
static inline void adj_guesttime(u64 hosttime, u64 reftime, u8 adj_flags)
|
||||||
{
|
{
|
||||||
|
unsigned long flags;
|
||||||
|
u64 cur_reftime;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This check is safe since we are executing in the
|
* This check is safe since we are executing in the
|
||||||
* interrupt context and time synch messages arre always
|
* interrupt context and time synch messages are always
|
||||||
* delivered on the same CPU.
|
* delivered on the same CPU.
|
||||||
*/
|
*/
|
||||||
|
if (adj_flags & ICTIMESYNCFLAG_SYNC) {
|
||||||
|
/* Queue a job to do do_settimeofday64() */
|
||||||
if (work_pending(&wrk.work))
|
if (work_pending(&wrk.work))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
wrk.host_time = hosttime;
|
wrk.host_time = hosttime;
|
||||||
wrk.ref_time = reftime;
|
wrk.ref_time = reftime;
|
||||||
wrk.flags = flags;
|
wrk.flags = adj_flags;
|
||||||
if ((flags & (ICTIMESYNCFLAG_SYNC | ICTIMESYNCFLAG_SAMPLE)) != 0) {
|
|
||||||
schedule_work(&wrk.work);
|
schedule_work(&wrk.work);
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* Save the adjusted time sample from the host and the snapshot
|
||||||
|
* of the current system time for PTP device.
|
||||||
|
*/
|
||||||
|
spin_lock_irqsave(&host_ts.lock, flags);
|
||||||
|
|
||||||
|
cur_reftime = hyperv_cs->read(hyperv_cs);
|
||||||
|
host_ts.host_time = hosttime;
|
||||||
|
host_ts.ref_time = cur_reftime;
|
||||||
|
ktime_get_snapshot(&host_ts.snap);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TimeSync v4 messages contain reference time (guest's Hyper-V
|
||||||
|
* clocksource read when the time sample was generated), we can
|
||||||
|
* improve the precision by adding the delta between now and the
|
||||||
|
* time of generation.
|
||||||
|
*/
|
||||||
|
if (ts_srv_version > TS_VERSION_3)
|
||||||
|
host_ts.host_time += (cur_reftime - reftime);
|
||||||
|
|
||||||
|
spin_unlock_irqrestore(&host_ts.lock, flags);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -253,7 +308,6 @@ static void timesync_onchannelcallback(void *context)
|
||||||
struct ictimesync_data *timedatap;
|
struct ictimesync_data *timedatap;
|
||||||
struct ictimesync_ref_data *refdata;
|
struct ictimesync_ref_data *refdata;
|
||||||
u8 *time_txf_buf = util_timesynch.recv_buffer;
|
u8 *time_txf_buf = util_timesynch.recv_buffer;
|
||||||
struct icmsg_negotiate *negop = NULL;
|
|
||||||
|
|
||||||
vmbus_recvpacket(channel, time_txf_buf,
|
vmbus_recvpacket(channel, time_txf_buf,
|
||||||
PAGE_SIZE, &recvlen, &requestid);
|
PAGE_SIZE, &recvlen, &requestid);
|
||||||
|
@ -263,12 +317,14 @@ static void timesync_onchannelcallback(void *context)
|
||||||
sizeof(struct vmbuspipe_hdr)];
|
sizeof(struct vmbuspipe_hdr)];
|
||||||
|
|
||||||
if (icmsghdrp->icmsgtype == ICMSGTYPE_NEGOTIATE) {
|
if (icmsghdrp->icmsgtype == ICMSGTYPE_NEGOTIATE) {
|
||||||
vmbus_prep_negotiate_resp(icmsghdrp, negop,
|
if (vmbus_prep_negotiate_resp(icmsghdrp, time_txf_buf,
|
||||||
time_txf_buf,
|
fw_versions, FW_VER_COUNT,
|
||||||
util_fw_version,
|
ts_versions, TS_VER_COUNT,
|
||||||
ts_srv_version);
|
NULL, &ts_srv_version)) {
|
||||||
pr_info("Using TimeSync version %d.%d\n",
|
pr_info("TimeSync IC version %d.%d\n",
|
||||||
ts_srv_version >> 16, ts_srv_version & 0xFFFF);
|
ts_srv_version >> 16,
|
||||||
|
ts_srv_version & 0xFFFF);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (ts_srv_version > TS_VERSION_3) {
|
if (ts_srv_version > TS_VERSION_3) {
|
||||||
refdata = (struct ictimesync_ref_data *)
|
refdata = (struct ictimesync_ref_data *)
|
||||||
|
@ -312,7 +368,6 @@ static void heartbeat_onchannelcallback(void *context)
|
||||||
struct icmsg_hdr *icmsghdrp;
|
struct icmsg_hdr *icmsghdrp;
|
||||||
struct heartbeat_msg_data *heartbeat_msg;
|
struct heartbeat_msg_data *heartbeat_msg;
|
||||||
u8 *hbeat_txf_buf = util_heartbeat.recv_buffer;
|
u8 *hbeat_txf_buf = util_heartbeat.recv_buffer;
|
||||||
struct icmsg_negotiate *negop = NULL;
|
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
|
|
||||||
|
@ -326,9 +381,16 @@ static void heartbeat_onchannelcallback(void *context)
|
||||||
sizeof(struct vmbuspipe_hdr)];
|
sizeof(struct vmbuspipe_hdr)];
|
||||||
|
|
||||||
if (icmsghdrp->icmsgtype == ICMSGTYPE_NEGOTIATE) {
|
if (icmsghdrp->icmsgtype == ICMSGTYPE_NEGOTIATE) {
|
||||||
vmbus_prep_negotiate_resp(icmsghdrp, negop,
|
if (vmbus_prep_negotiate_resp(icmsghdrp,
|
||||||
hbeat_txf_buf, util_fw_version,
|
hbeat_txf_buf,
|
||||||
hb_srv_version);
|
fw_versions, FW_VER_COUNT,
|
||||||
|
hb_versions, HB_VER_COUNT,
|
||||||
|
NULL, &hb_srv_version)) {
|
||||||
|
|
||||||
|
pr_info("Heartbeat IC version %d.%d\n",
|
||||||
|
hb_srv_version >> 16,
|
||||||
|
hb_srv_version & 0xFFFF);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
heartbeat_msg =
|
heartbeat_msg =
|
||||||
(struct heartbeat_msg_data *)&hbeat_txf_buf[
|
(struct heartbeat_msg_data *)&hbeat_txf_buf[
|
||||||
|
@ -373,38 +435,10 @@ static int util_probe(struct hv_device *dev,
|
||||||
* Turn off batched reading for all util drivers before we open the
|
* Turn off batched reading for all util drivers before we open the
|
||||||
* channel.
|
* channel.
|
||||||
*/
|
*/
|
||||||
|
set_channel_read_mode(dev->channel, HV_CALL_DIRECT);
|
||||||
set_channel_read_state(dev->channel, false);
|
|
||||||
|
|
||||||
hv_set_drvdata(dev, srv);
|
hv_set_drvdata(dev, srv);
|
||||||
|
|
||||||
/*
|
|
||||||
* Based on the host; initialize the framework and
|
|
||||||
* service version numbers we will negotiate.
|
|
||||||
*/
|
|
||||||
switch (vmbus_proto_version) {
|
|
||||||
case (VERSION_WS2008):
|
|
||||||
util_fw_version = UTIL_WS2K8_FW_VERSION;
|
|
||||||
sd_srv_version = SD_VERSION_1;
|
|
||||||
ts_srv_version = TS_VERSION_1;
|
|
||||||
hb_srv_version = HB_VERSION_1;
|
|
||||||
break;
|
|
||||||
case VERSION_WIN7:
|
|
||||||
case VERSION_WIN8:
|
|
||||||
case VERSION_WIN8_1:
|
|
||||||
util_fw_version = UTIL_FW_VERSION;
|
|
||||||
sd_srv_version = SD_VERSION;
|
|
||||||
ts_srv_version = TS_VERSION_3;
|
|
||||||
hb_srv_version = HB_VERSION;
|
|
||||||
break;
|
|
||||||
case VERSION_WIN10:
|
|
||||||
default:
|
|
||||||
util_fw_version = UTIL_FW_VERSION;
|
|
||||||
sd_srv_version = SD_VERSION;
|
|
||||||
ts_srv_version = TS_VERSION;
|
|
||||||
hb_srv_version = HB_VERSION;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = vmbus_open(dev->channel, 4 * PAGE_SIZE, 4 * PAGE_SIZE, NULL, 0,
|
ret = vmbus_open(dev->channel, 4 * PAGE_SIZE, 4 * PAGE_SIZE, NULL, 0,
|
||||||
srv->util_cb, dev->channel);
|
srv->util_cb, dev->channel);
|
||||||
if (ret)
|
if (ret)
|
||||||
|
@ -470,14 +504,113 @@ static struct hv_driver util_drv = {
|
||||||
.remove = util_remove,
|
.remove = util_remove,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static int hv_ptp_enable(struct ptp_clock_info *info,
|
||||||
|
struct ptp_clock_request *request, int on)
|
||||||
|
{
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int hv_ptp_settime(struct ptp_clock_info *p, const struct timespec64 *ts)
|
||||||
|
{
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int hv_ptp_adjfreq(struct ptp_clock_info *ptp, s32 delta)
|
||||||
|
{
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
|
static int hv_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
|
||||||
|
{
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int hv_ptp_gettime(struct ptp_clock_info *info, struct timespec64 *ts)
|
||||||
|
{
|
||||||
|
unsigned long flags;
|
||||||
|
u64 newtime, reftime;
|
||||||
|
|
||||||
|
spin_lock_irqsave(&host_ts.lock, flags);
|
||||||
|
reftime = hyperv_cs->read(hyperv_cs);
|
||||||
|
newtime = host_ts.host_time + (reftime - host_ts.ref_time);
|
||||||
|
*ts = ns_to_timespec64((newtime - WLTIMEDELTA) * 100);
|
||||||
|
spin_unlock_irqrestore(&host_ts.lock, flags);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int hv_ptp_get_syncdevicetime(ktime_t *device,
|
||||||
|
struct system_counterval_t *system,
|
||||||
|
void *ctx)
|
||||||
|
{
|
||||||
|
system->cs = hyperv_cs;
|
||||||
|
system->cycles = host_ts.ref_time;
|
||||||
|
*device = ns_to_ktime((host_ts.host_time - WLTIMEDELTA) * 100);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int hv_ptp_getcrosststamp(struct ptp_clock_info *ptp,
|
||||||
|
struct system_device_crosststamp *xtstamp)
|
||||||
|
{
|
||||||
|
unsigned long flags;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
spin_lock_irqsave(&host_ts.lock, flags);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* host_ts contains the last time sample from the host and the snapshot
|
||||||
|
* of system time. We don't need to calculate the time delta between
|
||||||
|
* the reception and now as get_device_system_crosststamp() does the
|
||||||
|
* required interpolation.
|
||||||
|
*/
|
||||||
|
ret = get_device_system_crosststamp(hv_ptp_get_syncdevicetime,
|
||||||
|
NULL, &host_ts.snap, xtstamp);
|
||||||
|
|
||||||
|
spin_unlock_irqrestore(&host_ts.lock, flags);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct ptp_clock_info ptp_hyperv_info = {
|
||||||
|
.name = "hyperv",
|
||||||
|
.enable = hv_ptp_enable,
|
||||||
|
.adjtime = hv_ptp_adjtime,
|
||||||
|
.adjfreq = hv_ptp_adjfreq,
|
||||||
|
.gettime64 = hv_ptp_gettime,
|
||||||
|
.getcrosststamp = hv_ptp_getcrosststamp,
|
||||||
|
.settime64 = hv_ptp_settime,
|
||||||
|
.owner = THIS_MODULE,
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct ptp_clock *hv_ptp_clock;
|
||||||
|
|
||||||
static int hv_timesync_init(struct hv_util_service *srv)
|
static int hv_timesync_init(struct hv_util_service *srv)
|
||||||
{
|
{
|
||||||
|
/* TimeSync requires Hyper-V clocksource. */
|
||||||
|
if (!hyperv_cs)
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
INIT_WORK(&wrk.work, hv_set_host_time);
|
INIT_WORK(&wrk.work, hv_set_host_time);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ptp_clock_register() returns NULL when CONFIG_PTP_1588_CLOCK is
|
||||||
|
* disabled but the driver is still useful without the PTP device
|
||||||
|
* as it still handles the ICTIMESYNCFLAG_SYNC case.
|
||||||
|
*/
|
||||||
|
hv_ptp_clock = ptp_clock_register(&ptp_hyperv_info, NULL);
|
||||||
|
if (IS_ERR_OR_NULL(hv_ptp_clock)) {
|
||||||
|
pr_err("cannot register PTP clock: %ld\n",
|
||||||
|
PTR_ERR(hv_ptp_clock));
|
||||||
|
hv_ptp_clock = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void hv_timesync_deinit(void)
|
static void hv_timesync_deinit(void)
|
||||||
{
|
{
|
||||||
|
if (hv_ptp_clock)
|
||||||
|
ptp_clock_unregister(hv_ptp_clock);
|
||||||
cancel_work_sync(&wrk.work);
|
cancel_work_sync(&wrk.work);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@
|
||||||
#include <asm/sync_bitops.h>
|
#include <asm/sync_bitops.h>
|
||||||
#include <linux/atomic.h>
|
#include <linux/atomic.h>
|
||||||
#include <linux/hyperv.h>
|
#include <linux/hyperv.h>
|
||||||
|
#include <linux/interrupt.h>
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Timeout for services such as KVP and fcopy.
|
* Timeout for services such as KVP and fcopy.
|
||||||
|
@ -40,95 +41,9 @@
|
||||||
*/
|
*/
|
||||||
#define HV_UTIL_NEGO_TIMEOUT 55
|
#define HV_UTIL_NEGO_TIMEOUT 55
|
||||||
|
|
||||||
/*
|
|
||||||
* The below CPUID leaves are present if VersionAndFeatures.HypervisorPresent
|
|
||||||
* is set by CPUID(HVCPUID_VERSION_FEATURES).
|
|
||||||
*/
|
|
||||||
enum hv_cpuid_function {
|
|
||||||
HVCPUID_VERSION_FEATURES = 0x00000001,
|
|
||||||
HVCPUID_VENDOR_MAXFUNCTION = 0x40000000,
|
|
||||||
HVCPUID_INTERFACE = 0x40000001,
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The remaining functions depend on the value of
|
|
||||||
* HVCPUID_INTERFACE
|
|
||||||
*/
|
|
||||||
HVCPUID_VERSION = 0x40000002,
|
|
||||||
HVCPUID_FEATURES = 0x40000003,
|
|
||||||
HVCPUID_ENLIGHTENMENT_INFO = 0x40000004,
|
|
||||||
HVCPUID_IMPLEMENTATION_LIMITS = 0x40000005,
|
|
||||||
};
|
|
||||||
|
|
||||||
#define HV_FEATURE_GUEST_CRASH_MSR_AVAILABLE 0x400
|
|
||||||
|
|
||||||
#define HV_X64_MSR_CRASH_P0 0x40000100
|
|
||||||
#define HV_X64_MSR_CRASH_P1 0x40000101
|
|
||||||
#define HV_X64_MSR_CRASH_P2 0x40000102
|
|
||||||
#define HV_X64_MSR_CRASH_P3 0x40000103
|
|
||||||
#define HV_X64_MSR_CRASH_P4 0x40000104
|
|
||||||
#define HV_X64_MSR_CRASH_CTL 0x40000105
|
|
||||||
|
|
||||||
#define HV_CRASH_CTL_CRASH_NOTIFY (1ULL << 63)
|
|
||||||
|
|
||||||
/* Define version of the synthetic interrupt controller. */
|
|
||||||
#define HV_SYNIC_VERSION (1)
|
|
||||||
|
|
||||||
#define HV_ANY_VP (0xFFFFFFFF)
|
|
||||||
|
|
||||||
/* Define synthetic interrupt controller flag constants. */
|
/* Define synthetic interrupt controller flag constants. */
|
||||||
#define HV_EVENT_FLAGS_COUNT (256 * 8)
|
#define HV_EVENT_FLAGS_COUNT (256 * 8)
|
||||||
#define HV_EVENT_FLAGS_BYTE_COUNT (256)
|
#define HV_EVENT_FLAGS_LONG_COUNT (256 / sizeof(unsigned long))
|
||||||
#define HV_EVENT_FLAGS_DWORD_COUNT (256 / sizeof(u32))
|
|
||||||
|
|
||||||
/* Define invalid partition identifier. */
|
|
||||||
#define HV_PARTITION_ID_INVALID ((u64)0x0)
|
|
||||||
|
|
||||||
/* Define port type. */
|
|
||||||
enum hv_port_type {
|
|
||||||
HVPORT_MSG = 1,
|
|
||||||
HVPORT_EVENT = 2,
|
|
||||||
HVPORT_MONITOR = 3
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Define port information structure. */
|
|
||||||
struct hv_port_info {
|
|
||||||
enum hv_port_type port_type;
|
|
||||||
u32 padding;
|
|
||||||
union {
|
|
||||||
struct {
|
|
||||||
u32 target_sint;
|
|
||||||
u32 target_vp;
|
|
||||||
u64 rsvdz;
|
|
||||||
} message_port_info;
|
|
||||||
struct {
|
|
||||||
u32 target_sint;
|
|
||||||
u32 target_vp;
|
|
||||||
u16 base_flag_number;
|
|
||||||
u16 flag_count;
|
|
||||||
u32 rsvdz;
|
|
||||||
} event_port_info;
|
|
||||||
struct {
|
|
||||||
u64 monitor_address;
|
|
||||||
u64 rsvdz;
|
|
||||||
} monitor_port_info;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
struct hv_connection_info {
|
|
||||||
enum hv_port_type port_type;
|
|
||||||
u32 padding;
|
|
||||||
union {
|
|
||||||
struct {
|
|
||||||
u64 rsvdz;
|
|
||||||
} message_connection_info;
|
|
||||||
struct {
|
|
||||||
u64 rsvdz;
|
|
||||||
} event_connection_info;
|
|
||||||
struct {
|
|
||||||
u64 monitor_address;
|
|
||||||
} monitor_connection_info;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Timer configuration register.
|
* Timer configuration register.
|
||||||
|
@ -146,18 +61,10 @@ union hv_timer_config {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Define the number of message buffers associated with each port. */
|
|
||||||
#define HV_PORT_MESSAGE_BUFFER_COUNT (16)
|
|
||||||
|
|
||||||
/* Define the synthetic interrupt controller event flags format. */
|
/* Define the synthetic interrupt controller event flags format. */
|
||||||
union hv_synic_event_flags {
|
union hv_synic_event_flags {
|
||||||
u8 flags8[HV_EVENT_FLAGS_BYTE_COUNT];
|
unsigned long flags[HV_EVENT_FLAGS_LONG_COUNT];
|
||||||
u32 flags32[HV_EVENT_FLAGS_DWORD_COUNT];
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Define the synthetic interrupt flags page layout. */
|
|
||||||
struct hv_synic_event_flags_page {
|
|
||||||
union hv_synic_event_flags sintevent_flags[HV_SYNIC_SINT_COUNT];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Define SynIC control register. */
|
/* Define SynIC control register. */
|
||||||
|
@ -261,6 +168,8 @@ struct hv_monitor_page {
|
||||||
u8 rsvdz4[1984];
|
u8 rsvdz4[1984];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#define HV_HYPERCALL_PARAM_ALIGN sizeof(u64)
|
||||||
|
|
||||||
/* Definition of the hv_post_message hypercall input structure. */
|
/* Definition of the hv_post_message hypercall input structure. */
|
||||||
struct hv_input_post_message {
|
struct hv_input_post_message {
|
||||||
union hv_connection_id connectionid;
|
union hv_connection_id connectionid;
|
||||||
|
@ -270,56 +179,6 @@ struct hv_input_post_message {
|
||||||
u64 payload[HV_MESSAGE_PAYLOAD_QWORD_COUNT];
|
u64 payload[HV_MESSAGE_PAYLOAD_QWORD_COUNT];
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
|
||||||
* Versioning definitions used for guests reporting themselves to the
|
|
||||||
* hypervisor, and visa versa.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* Version info reported by guest OS's */
|
|
||||||
enum hv_guest_os_vendor {
|
|
||||||
HVGUESTOS_VENDOR_MICROSOFT = 0x0001
|
|
||||||
};
|
|
||||||
|
|
||||||
enum hv_guest_os_microsoft_ids {
|
|
||||||
HVGUESTOS_MICROSOFT_UNDEFINED = 0x00,
|
|
||||||
HVGUESTOS_MICROSOFT_MSDOS = 0x01,
|
|
||||||
HVGUESTOS_MICROSOFT_WINDOWS3X = 0x02,
|
|
||||||
HVGUESTOS_MICROSOFT_WINDOWS9X = 0x03,
|
|
||||||
HVGUESTOS_MICROSOFT_WINDOWSNT = 0x04,
|
|
||||||
HVGUESTOS_MICROSOFT_WINDOWSCE = 0x05
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Declare the MSR used to identify the guest OS.
|
|
||||||
*/
|
|
||||||
#define HV_X64_MSR_GUEST_OS_ID 0x40000000
|
|
||||||
|
|
||||||
union hv_x64_msr_guest_os_id_contents {
|
|
||||||
u64 as_uint64;
|
|
||||||
struct {
|
|
||||||
u64 build_number:16;
|
|
||||||
u64 service_version:8; /* Service Pack, etc. */
|
|
||||||
u64 minor_version:8;
|
|
||||||
u64 major_version:8;
|
|
||||||
u64 os_id:8; /* enum hv_guest_os_microsoft_ids (if Vendor=MS) */
|
|
||||||
u64 vendor_id:16; /* enum hv_guest_os_vendor */
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Declare the MSR used to setup pages used to communicate with the hypervisor.
|
|
||||||
*/
|
|
||||||
#define HV_X64_MSR_HYPERCALL 0x40000001
|
|
||||||
|
|
||||||
union hv_x64_msr_hypercall_contents {
|
|
||||||
u64 as_uint64;
|
|
||||||
struct {
|
|
||||||
u64 enable:1;
|
|
||||||
u64 reserved:11;
|
|
||||||
u64 guest_physical_address:52;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
VMBUS_MESSAGE_CONNECTION_ID = 1,
|
VMBUS_MESSAGE_CONNECTION_ID = 1,
|
||||||
|
@ -331,111 +190,44 @@ enum {
|
||||||
VMBUS_MESSAGE_SINT = 2,
|
VMBUS_MESSAGE_SINT = 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
/* #defines */
|
/*
|
||||||
|
* Per cpu state for channel handling
|
||||||
#define HV_PRESENT_BIT 0x80000000
|
*/
|
||||||
|
struct hv_per_cpu_context {
|
||||||
|
void *synic_message_page;
|
||||||
|
void *synic_event_page;
|
||||||
|
/*
|
||||||
|
* buffer to post messages to the host.
|
||||||
|
*/
|
||||||
|
void *post_msg_page;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The guest OS needs to register the guest ID with the hypervisor.
|
* Starting with win8, we can take channel interrupts on any CPU;
|
||||||
* The guest ID is a 64 bit entity and the structure of this ID is
|
* we will manage the tasklet that handles events messages on a per CPU
|
||||||
* specified in the Hyper-V specification:
|
* basis.
|
||||||
*
|
|
||||||
* http://msdn.microsoft.com/en-us/library/windows/hardware/ff542653%28v=vs.85%29.aspx
|
|
||||||
*
|
|
||||||
* While the current guideline does not specify how Linux guest ID(s)
|
|
||||||
* need to be generated, our plan is to publish the guidelines for
|
|
||||||
* Linux and other guest operating systems that currently are hosted
|
|
||||||
* on Hyper-V. The implementation here conforms to this yet
|
|
||||||
* unpublished guidelines.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* Bit(s)
|
|
||||||
* 63 - Indicates if the OS is Open Source or not; 1 is Open Source
|
|
||||||
* 62:56 - Os Type; Linux is 0x100
|
|
||||||
* 55:48 - Distro specific identification
|
|
||||||
* 47:16 - Linux kernel version number
|
|
||||||
* 15:0 - Distro specific identification
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
|
struct tasklet_struct msg_dpc;
|
||||||
#define HV_LINUX_VENDOR_ID 0x8100
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Generate the guest ID based on the guideline described above.
|
* To optimize the mapping of relid to channel, maintain
|
||||||
|
* per-cpu list of the channels based on their CPU affinity.
|
||||||
*/
|
*/
|
||||||
|
struct list_head chan_list;
|
||||||
static inline __u64 generate_guest_id(__u8 d_info1, __u32 kernel_version,
|
struct clock_event_device *clk_evt;
|
||||||
__u16 d_info2)
|
|
||||||
{
|
|
||||||
__u64 guest_id = 0;
|
|
||||||
|
|
||||||
guest_id = (((__u64)HV_LINUX_VENDOR_ID) << 48);
|
|
||||||
guest_id |= (((__u64)(d_info1)) << 48);
|
|
||||||
guest_id |= (((__u64)(kernel_version)) << 16);
|
|
||||||
guest_id |= ((__u64)(d_info2));
|
|
||||||
|
|
||||||
return guest_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#define HV_CPU_POWER_MANAGEMENT (1 << 0)
|
|
||||||
#define HV_RECOMMENDATIONS_MAX 4
|
|
||||||
|
|
||||||
#define HV_X64_MAX 5
|
|
||||||
#define HV_CAPS_MAX 8
|
|
||||||
|
|
||||||
|
|
||||||
#define HV_HYPERCALL_PARAM_ALIGN sizeof(u64)
|
|
||||||
|
|
||||||
|
|
||||||
/* Service definitions */
|
|
||||||
|
|
||||||
#define HV_SERVICE_PARENT_PORT (0)
|
|
||||||
#define HV_SERVICE_PARENT_CONNECTION (0)
|
|
||||||
|
|
||||||
#define HV_SERVICE_CONNECT_RESPONSE_SUCCESS (0)
|
|
||||||
#define HV_SERVICE_CONNECT_RESPONSE_INVALID_PARAMETER (1)
|
|
||||||
#define HV_SERVICE_CONNECT_RESPONSE_UNKNOWN_SERVICE (2)
|
|
||||||
#define HV_SERVICE_CONNECT_RESPONSE_CONNECTION_REJECTED (3)
|
|
||||||
|
|
||||||
#define HV_SERVICE_CONNECT_REQUEST_MESSAGE_ID (1)
|
|
||||||
#define HV_SERVICE_CONNECT_RESPONSE_MESSAGE_ID (2)
|
|
||||||
#define HV_SERVICE_DISCONNECT_REQUEST_MESSAGE_ID (3)
|
|
||||||
#define HV_SERVICE_DISCONNECT_RESPONSE_MESSAGE_ID (4)
|
|
||||||
#define HV_SERVICE_MAX_MESSAGE_ID (4)
|
|
||||||
|
|
||||||
#define HV_SERVICE_PROTOCOL_VERSION (0x0010)
|
|
||||||
#define HV_CONNECT_PAYLOAD_BYTE_COUNT 64
|
|
||||||
|
|
||||||
/* #define VMBUS_REVISION_NUMBER 6 */
|
|
||||||
|
|
||||||
/* Our local vmbus's port and connection id. Anything >0 is fine */
|
|
||||||
/* #define VMBUS_PORT_ID 11 */
|
|
||||||
|
|
||||||
/* 628180B8-308D-4c5e-B7DB-1BEB62E62EF4 */
|
|
||||||
static const uuid_le VMBUS_SERVICE_ID = {
|
|
||||||
.b = {
|
|
||||||
0xb8, 0x80, 0x81, 0x62, 0x8d, 0x30, 0x5e, 0x4c,
|
|
||||||
0xb7, 0xdb, 0x1b, 0xeb, 0x62, 0xe6, 0x2e, 0xf4
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
struct hv_context {
|
struct hv_context {
|
||||||
/* We only support running on top of Hyper-V
|
/* We only support running on top of Hyper-V
|
||||||
* So at this point this really can only contain the Hyper-V ID
|
* So at this point this really can only contain the Hyper-V ID
|
||||||
*/
|
*/
|
||||||
u64 guestid;
|
u64 guestid;
|
||||||
|
|
||||||
void *hypercall_page;
|
|
||||||
void *tsc_page;
|
void *tsc_page;
|
||||||
|
|
||||||
bool synic_initialized;
|
bool synic_initialized;
|
||||||
|
|
||||||
void *synic_message_page[NR_CPUS];
|
struct hv_per_cpu_context __percpu *cpu_context;
|
||||||
void *synic_event_page[NR_CPUS];
|
|
||||||
/*
|
/*
|
||||||
* Hypervisor's notion of virtual processor ID is different from
|
* Hypervisor's notion of virtual processor ID is different from
|
||||||
* Linux' notion of CPU ID. This information can only be retrieved
|
* Linux' notion of CPU ID. This information can only be retrieved
|
||||||
|
@ -446,26 +238,7 @@ struct hv_context {
|
||||||
* Linux cpuid 'a'.
|
* Linux cpuid 'a'.
|
||||||
*/
|
*/
|
||||||
u32 vp_index[NR_CPUS];
|
u32 vp_index[NR_CPUS];
|
||||||
/*
|
|
||||||
* Starting with win8, we can take channel interrupts on any CPU;
|
|
||||||
* we will manage the tasklet that handles events messages on a per CPU
|
|
||||||
* basis.
|
|
||||||
*/
|
|
||||||
struct tasklet_struct *event_dpc[NR_CPUS];
|
|
||||||
struct tasklet_struct *msg_dpc[NR_CPUS];
|
|
||||||
/*
|
|
||||||
* To optimize the mapping of relid to channel, maintain
|
|
||||||
* per-cpu list of the channels based on their CPU affinity.
|
|
||||||
*/
|
|
||||||
struct list_head percpu_list[NR_CPUS];
|
|
||||||
/*
|
|
||||||
* buffer to post messages to the host.
|
|
||||||
*/
|
|
||||||
void *post_msg_page[NR_CPUS];
|
|
||||||
/*
|
|
||||||
* Support PV clockevent device.
|
|
||||||
*/
|
|
||||||
struct clock_event_device *clk_evt[NR_CPUS];
|
|
||||||
/*
|
/*
|
||||||
* To manage allocations in a NUMA node.
|
* To manage allocations in a NUMA node.
|
||||||
* Array indexed by numa node ID.
|
* Array indexed by numa node ID.
|
||||||
|
@ -475,14 +248,6 @@ struct hv_context {
|
||||||
|
|
||||||
extern struct hv_context hv_context;
|
extern struct hv_context hv_context;
|
||||||
|
|
||||||
struct ms_hyperv_tsc_page {
|
|
||||||
volatile u32 tsc_sequence;
|
|
||||||
u32 reserved1;
|
|
||||||
volatile u64 tsc_scale;
|
|
||||||
volatile s64 tsc_offset;
|
|
||||||
u64 reserved2[509];
|
|
||||||
};
|
|
||||||
|
|
||||||
struct hv_ring_buffer_debug_info {
|
struct hv_ring_buffer_debug_info {
|
||||||
u32 current_interrupt_mask;
|
u32 current_interrupt_mask;
|
||||||
u32 current_read_index;
|
u32 current_read_index;
|
||||||
|
@ -495,8 +260,6 @@ struct hv_ring_buffer_debug_info {
|
||||||
|
|
||||||
extern int hv_init(void);
|
extern int hv_init(void);
|
||||||
|
|
||||||
extern void hv_cleanup(bool crash);
|
|
||||||
|
|
||||||
extern int hv_post_message(union hv_connection_id connection_id,
|
extern int hv_post_message(union hv_connection_id connection_id,
|
||||||
enum hv_message_type message_type,
|
enum hv_message_type message_type,
|
||||||
void *payload, size_t payload_size);
|
void *payload, size_t payload_size);
|
||||||
|
@ -505,20 +268,12 @@ extern int hv_synic_alloc(void);
|
||||||
|
|
||||||
extern void hv_synic_free(void);
|
extern void hv_synic_free(void);
|
||||||
|
|
||||||
extern void hv_synic_init(void *irqarg);
|
extern int hv_synic_init(unsigned int cpu);
|
||||||
|
|
||||||
extern void hv_synic_cleanup(void *arg);
|
extern int hv_synic_cleanup(unsigned int cpu);
|
||||||
|
|
||||||
extern void hv_synic_clockevents_cleanup(void);
|
extern void hv_synic_clockevents_cleanup(void);
|
||||||
|
|
||||||
/*
|
|
||||||
* Host version information.
|
|
||||||
*/
|
|
||||||
extern unsigned int host_info_eax;
|
|
||||||
extern unsigned int host_info_ebx;
|
|
||||||
extern unsigned int host_info_ecx;
|
|
||||||
extern unsigned int host_info_edx;
|
|
||||||
|
|
||||||
/* Interface */
|
/* Interface */
|
||||||
|
|
||||||
|
|
||||||
|
@ -528,21 +283,15 @@ int hv_ringbuffer_init(struct hv_ring_buffer_info *ring_info,
|
||||||
void hv_ringbuffer_cleanup(struct hv_ring_buffer_info *ring_info);
|
void hv_ringbuffer_cleanup(struct hv_ring_buffer_info *ring_info);
|
||||||
|
|
||||||
int hv_ringbuffer_write(struct vmbus_channel *channel,
|
int hv_ringbuffer_write(struct vmbus_channel *channel,
|
||||||
struct kvec *kv_list,
|
const struct kvec *kv_list, u32 kv_count);
|
||||||
u32 kv_count, bool lock,
|
|
||||||
bool kick_q);
|
|
||||||
|
|
||||||
int hv_ringbuffer_read(struct vmbus_channel *channel,
|
int hv_ringbuffer_read(struct vmbus_channel *channel,
|
||||||
void *buffer, u32 buflen, u32 *buffer_actual_len,
|
void *buffer, u32 buflen, u32 *buffer_actual_len,
|
||||||
u64 *requestid, bool raw);
|
u64 *requestid, bool raw);
|
||||||
|
|
||||||
void hv_ringbuffer_get_debuginfo(struct hv_ring_buffer_info *ring_info,
|
void hv_ringbuffer_get_debuginfo(const struct hv_ring_buffer_info *ring_info,
|
||||||
struct hv_ring_buffer_debug_info *debug_info);
|
struct hv_ring_buffer_debug_info *debug_info);
|
||||||
|
|
||||||
void hv_begin_read(struct hv_ring_buffer_info *rbi);
|
|
||||||
|
|
||||||
u32 hv_end_read(struct hv_ring_buffer_info *rbi);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Maximum channels is determined by the size of the interrupt page
|
* Maximum channels is determined by the size of the interrupt page
|
||||||
* which is PAGE_SIZE. 1/2 of PAGE_SIZE is for send endpoint interrupt
|
* which is PAGE_SIZE. 1/2 of PAGE_SIZE is for send endpoint interrupt
|
||||||
|
@ -608,6 +357,11 @@ struct vmbus_msginfo {
|
||||||
|
|
||||||
extern struct vmbus_connection vmbus_connection;
|
extern struct vmbus_connection vmbus_connection;
|
||||||
|
|
||||||
|
static inline void vmbus_send_interrupt(u32 relid)
|
||||||
|
{
|
||||||
|
sync_set_bit(relid, vmbus_connection.send_int_page);
|
||||||
|
}
|
||||||
|
|
||||||
enum vmbus_message_handler_type {
|
enum vmbus_message_handler_type {
|
||||||
/* The related handler can sleep. */
|
/* The related handler can sleep. */
|
||||||
VMHT_BLOCKING = 0,
|
VMHT_BLOCKING = 0,
|
||||||
|
@ -625,41 +379,6 @@ struct vmbus_channel_message_table_entry {
|
||||||
extern struct vmbus_channel_message_table_entry
|
extern struct vmbus_channel_message_table_entry
|
||||||
channel_message_table[CHANNELMSG_COUNT];
|
channel_message_table[CHANNELMSG_COUNT];
|
||||||
|
|
||||||
/* Free the message slot and signal end-of-message if required */
|
|
||||||
static inline void vmbus_signal_eom(struct hv_message *msg, u32 old_msg_type)
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* On crash we're reading some other CPU's message page and we need
|
|
||||||
* to be careful: this other CPU may already had cleared the header
|
|
||||||
* and the host may already had delivered some other message there.
|
|
||||||
* In case we blindly write msg->header.message_type we're going
|
|
||||||
* to lose it. We can still lose a message of the same type but
|
|
||||||
* we count on the fact that there can only be one
|
|
||||||
* CHANNELMSG_UNLOAD_RESPONSE and we don't care about other messages
|
|
||||||
* on crash.
|
|
||||||
*/
|
|
||||||
if (cmpxchg(&msg->header.message_type, old_msg_type,
|
|
||||||
HVMSG_NONE) != old_msg_type)
|
|
||||||
return;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Make sure the write to MessageType (ie set to
|
|
||||||
* HVMSG_NONE) happens before we read the
|
|
||||||
* MessagePending and EOMing. Otherwise, the EOMing
|
|
||||||
* will not deliver any more messages since there is
|
|
||||||
* no empty slot
|
|
||||||
*/
|
|
||||||
mb();
|
|
||||||
|
|
||||||
if (msg->header.message_flags.msg_pending) {
|
|
||||||
/*
|
|
||||||
* This will cause message queue rescan to
|
|
||||||
* possibly deliver another msg from the
|
|
||||||
* hypervisor
|
|
||||||
*/
|
|
||||||
wrmsrl(HV_X64_MSR_EOM, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* General vmbus interface */
|
/* General vmbus interface */
|
||||||
|
|
||||||
|
@ -670,10 +389,6 @@ struct hv_device *vmbus_device_create(const uuid_le *type,
|
||||||
int vmbus_device_register(struct hv_device *child_device_obj);
|
int vmbus_device_register(struct hv_device *child_device_obj);
|
||||||
void vmbus_device_unregister(struct hv_device *device_obj);
|
void vmbus_device_unregister(struct hv_device *device_obj);
|
||||||
|
|
||||||
/* static void */
|
|
||||||
/* VmbusChildDeviceDestroy( */
|
|
||||||
/* struct hv_device *); */
|
|
||||||
|
|
||||||
struct vmbus_channel *relid2channel(u32 relid);
|
struct vmbus_channel *relid2channel(u32 relid);
|
||||||
|
|
||||||
void vmbus_free_channels(void);
|
void vmbus_free_channels(void);
|
||||||
|
@ -683,7 +398,7 @@ void vmbus_free_channels(void);
|
||||||
int vmbus_connect(void);
|
int vmbus_connect(void);
|
||||||
void vmbus_disconnect(void);
|
void vmbus_disconnect(void);
|
||||||
|
|
||||||
int vmbus_post_msg(void *buffer, size_t buflen);
|
int vmbus_post_msg(void *buffer, size_t buflen, bool can_sleep);
|
||||||
|
|
||||||
void vmbus_on_event(unsigned long data);
|
void vmbus_on_event(unsigned long data);
|
||||||
void vmbus_on_msg_dpc(unsigned long data);
|
void vmbus_on_msg_dpc(unsigned long data);
|
||||||
|
|
|
@ -32,26 +32,6 @@
|
||||||
|
|
||||||
#include "hyperv_vmbus.h"
|
#include "hyperv_vmbus.h"
|
||||||
|
|
||||||
void hv_begin_read(struct hv_ring_buffer_info *rbi)
|
|
||||||
{
|
|
||||||
rbi->ring_buffer->interrupt_mask = 1;
|
|
||||||
virt_mb();
|
|
||||||
}
|
|
||||||
|
|
||||||
u32 hv_end_read(struct hv_ring_buffer_info *rbi)
|
|
||||||
{
|
|
||||||
|
|
||||||
rbi->ring_buffer->interrupt_mask = 0;
|
|
||||||
virt_mb();
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Now check to see if the ring buffer is still empty.
|
|
||||||
* If it is not, we raced and we need to process new
|
|
||||||
* incoming messages.
|
|
||||||
*/
|
|
||||||
return hv_get_bytes_to_read(rbi);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* When we write to the ring buffer, check if the host needs to
|
* When we write to the ring buffer, check if the host needs to
|
||||||
* be signaled. Here is the details of this protocol:
|
* be signaled. Here is the details of this protocol:
|
||||||
|
@ -77,8 +57,7 @@ u32 hv_end_read(struct hv_ring_buffer_info *rbi)
|
||||||
* host logic is fixed.
|
* host logic is fixed.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static void hv_signal_on_write(u32 old_write, struct vmbus_channel *channel,
|
static void hv_signal_on_write(u32 old_write, struct vmbus_channel *channel)
|
||||||
bool kick_q)
|
|
||||||
{
|
{
|
||||||
struct hv_ring_buffer_info *rbi = &channel->outbound;
|
struct hv_ring_buffer_info *rbi = &channel->outbound;
|
||||||
|
|
||||||
|
@ -117,11 +96,9 @@ hv_set_next_write_location(struct hv_ring_buffer_info *ring_info,
|
||||||
|
|
||||||
/* Get the next read location for the specified ring buffer. */
|
/* Get the next read location for the specified ring buffer. */
|
||||||
static inline u32
|
static inline u32
|
||||||
hv_get_next_read_location(struct hv_ring_buffer_info *ring_info)
|
hv_get_next_read_location(const struct hv_ring_buffer_info *ring_info)
|
||||||
{
|
{
|
||||||
u32 next = ring_info->ring_buffer->read_index;
|
return ring_info->ring_buffer->read_index;
|
||||||
|
|
||||||
return next;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -129,13 +106,14 @@ hv_get_next_read_location(struct hv_ring_buffer_info *ring_info)
|
||||||
* This allows the caller to skip.
|
* This allows the caller to skip.
|
||||||
*/
|
*/
|
||||||
static inline u32
|
static inline u32
|
||||||
hv_get_next_readlocation_withoffset(struct hv_ring_buffer_info *ring_info,
|
hv_get_next_readlocation_withoffset(const struct hv_ring_buffer_info *ring_info,
|
||||||
u32 offset)
|
u32 offset)
|
||||||
{
|
{
|
||||||
u32 next = ring_info->ring_buffer->read_index;
|
u32 next = ring_info->ring_buffer->read_index;
|
||||||
|
|
||||||
next += offset;
|
next += offset;
|
||||||
next %= ring_info->ring_datasize;
|
if (next >= ring_info->ring_datasize)
|
||||||
|
next -= ring_info->ring_datasize;
|
||||||
|
|
||||||
return next;
|
return next;
|
||||||
}
|
}
|
||||||
|
@ -151,7 +129,7 @@ hv_set_next_read_location(struct hv_ring_buffer_info *ring_info,
|
||||||
|
|
||||||
/* Get the size of the ring buffer. */
|
/* Get the size of the ring buffer. */
|
||||||
static inline u32
|
static inline u32
|
||||||
hv_get_ring_buffersize(struct hv_ring_buffer_info *ring_info)
|
hv_get_ring_buffersize(const struct hv_ring_buffer_info *ring_info)
|
||||||
{
|
{
|
||||||
return ring_info->ring_datasize;
|
return ring_info->ring_datasize;
|
||||||
}
|
}
|
||||||
|
@ -168,7 +146,7 @@ hv_get_ring_bufferindices(struct hv_ring_buffer_info *ring_info)
|
||||||
* Assume there is enough room. Handles wrap-around in src case only!!
|
* Assume there is enough room. Handles wrap-around in src case only!!
|
||||||
*/
|
*/
|
||||||
static u32 hv_copyfrom_ringbuffer(
|
static u32 hv_copyfrom_ringbuffer(
|
||||||
struct hv_ring_buffer_info *ring_info,
|
const struct hv_ring_buffer_info *ring_info,
|
||||||
void *dest,
|
void *dest,
|
||||||
u32 destlen,
|
u32 destlen,
|
||||||
u32 start_read_offset)
|
u32 start_read_offset)
|
||||||
|
@ -179,7 +157,8 @@ static u32 hv_copyfrom_ringbuffer(
|
||||||
memcpy(dest, ring_buffer + start_read_offset, destlen);
|
memcpy(dest, ring_buffer + start_read_offset, destlen);
|
||||||
|
|
||||||
start_read_offset += destlen;
|
start_read_offset += destlen;
|
||||||
start_read_offset %= ring_buffer_size;
|
if (start_read_offset >= ring_buffer_size)
|
||||||
|
start_read_offset -= ring_buffer_size;
|
||||||
|
|
||||||
return start_read_offset;
|
return start_read_offset;
|
||||||
}
|
}
|
||||||
|
@ -192,7 +171,7 @@ static u32 hv_copyfrom_ringbuffer(
|
||||||
static u32 hv_copyto_ringbuffer(
|
static u32 hv_copyto_ringbuffer(
|
||||||
struct hv_ring_buffer_info *ring_info,
|
struct hv_ring_buffer_info *ring_info,
|
||||||
u32 start_write_offset,
|
u32 start_write_offset,
|
||||||
void *src,
|
const void *src,
|
||||||
u32 srclen)
|
u32 srclen)
|
||||||
{
|
{
|
||||||
void *ring_buffer = hv_get_ring_buffer(ring_info);
|
void *ring_buffer = hv_get_ring_buffer(ring_info);
|
||||||
|
@ -201,13 +180,14 @@ static u32 hv_copyto_ringbuffer(
|
||||||
memcpy(ring_buffer + start_write_offset, src, srclen);
|
memcpy(ring_buffer + start_write_offset, src, srclen);
|
||||||
|
|
||||||
start_write_offset += srclen;
|
start_write_offset += srclen;
|
||||||
start_write_offset %= ring_buffer_size;
|
if (start_write_offset >= ring_buffer_size)
|
||||||
|
start_write_offset -= ring_buffer_size;
|
||||||
|
|
||||||
return start_write_offset;
|
return start_write_offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Get various debug metrics for the specified ring buffer. */
|
/* Get various debug metrics for the specified ring buffer. */
|
||||||
void hv_ringbuffer_get_debuginfo(struct hv_ring_buffer_info *ring_info,
|
void hv_ringbuffer_get_debuginfo(const struct hv_ring_buffer_info *ring_info,
|
||||||
struct hv_ring_buffer_debug_info *debug_info)
|
struct hv_ring_buffer_debug_info *debug_info)
|
||||||
{
|
{
|
||||||
u32 bytes_avail_towrite;
|
u32 bytes_avail_towrite;
|
||||||
|
@ -285,8 +265,7 @@ void hv_ringbuffer_cleanup(struct hv_ring_buffer_info *ring_info)
|
||||||
|
|
||||||
/* Write to the ring buffer. */
|
/* Write to the ring buffer. */
|
||||||
int hv_ringbuffer_write(struct vmbus_channel *channel,
|
int hv_ringbuffer_write(struct vmbus_channel *channel,
|
||||||
struct kvec *kv_list, u32 kv_count, bool lock,
|
const struct kvec *kv_list, u32 kv_count)
|
||||||
bool kick_q)
|
|
||||||
{
|
{
|
||||||
int i = 0;
|
int i = 0;
|
||||||
u32 bytes_avail_towrite;
|
u32 bytes_avail_towrite;
|
||||||
|
@ -298,12 +277,14 @@ int hv_ringbuffer_write(struct vmbus_channel *channel,
|
||||||
unsigned long flags = 0;
|
unsigned long flags = 0;
|
||||||
struct hv_ring_buffer_info *outring_info = &channel->outbound;
|
struct hv_ring_buffer_info *outring_info = &channel->outbound;
|
||||||
|
|
||||||
|
if (channel->rescind)
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
for (i = 0; i < kv_count; i++)
|
for (i = 0; i < kv_count; i++)
|
||||||
totalbytes_towrite += kv_list[i].iov_len;
|
totalbytes_towrite += kv_list[i].iov_len;
|
||||||
|
|
||||||
totalbytes_towrite += sizeof(u64);
|
totalbytes_towrite += sizeof(u64);
|
||||||
|
|
||||||
if (lock)
|
|
||||||
spin_lock_irqsave(&outring_info->ring_lock, flags);
|
spin_lock_irqsave(&outring_info->ring_lock, flags);
|
||||||
|
|
||||||
bytes_avail_towrite = hv_get_bytes_to_write(outring_info);
|
bytes_avail_towrite = hv_get_bytes_to_write(outring_info);
|
||||||
|
@ -314,7 +295,6 @@ int hv_ringbuffer_write(struct vmbus_channel *channel,
|
||||||
* is empty since the read index == write index.
|
* is empty since the read index == write index.
|
||||||
*/
|
*/
|
||||||
if (bytes_avail_towrite <= totalbytes_towrite) {
|
if (bytes_avail_towrite <= totalbytes_towrite) {
|
||||||
if (lock)
|
|
||||||
spin_unlock_irqrestore(&outring_info->ring_lock, flags);
|
spin_unlock_irqrestore(&outring_info->ring_lock, flags);
|
||||||
return -EAGAIN;
|
return -EAGAIN;
|
||||||
}
|
}
|
||||||
|
@ -346,10 +326,13 @@ int hv_ringbuffer_write(struct vmbus_channel *channel,
|
||||||
hv_set_next_write_location(outring_info, next_write_location);
|
hv_set_next_write_location(outring_info, next_write_location);
|
||||||
|
|
||||||
|
|
||||||
if (lock)
|
|
||||||
spin_unlock_irqrestore(&outring_info->ring_lock, flags);
|
spin_unlock_irqrestore(&outring_info->ring_lock, flags);
|
||||||
|
|
||||||
hv_signal_on_write(old_write, channel, kick_q);
|
hv_signal_on_write(old_write, channel);
|
||||||
|
|
||||||
|
if (channel->rescind)
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -54,31 +54,7 @@ static struct acpi_device *hv_acpi_dev;
|
||||||
|
|
||||||
static struct completion probe_event;
|
static struct completion probe_event;
|
||||||
|
|
||||||
|
static int hyperv_cpuhp_online;
|
||||||
static void hyperv_report_panic(struct pt_regs *regs)
|
|
||||||
{
|
|
||||||
static bool panic_reported;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* We prefer to report panic on 'die' chain as we have proper
|
|
||||||
* registers to report, but if we miss it (e.g. on BUG()) we need
|
|
||||||
* to report it on 'panic'.
|
|
||||||
*/
|
|
||||||
if (panic_reported)
|
|
||||||
return;
|
|
||||||
panic_reported = true;
|
|
||||||
|
|
||||||
wrmsrl(HV_X64_MSR_CRASH_P0, regs->ip);
|
|
||||||
wrmsrl(HV_X64_MSR_CRASH_P1, regs->ax);
|
|
||||||
wrmsrl(HV_X64_MSR_CRASH_P2, regs->bx);
|
|
||||||
wrmsrl(HV_X64_MSR_CRASH_P3, regs->cx);
|
|
||||||
wrmsrl(HV_X64_MSR_CRASH_P4, regs->dx);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Let Hyper-V know there is crash data available
|
|
||||||
*/
|
|
||||||
wrmsrl(HV_X64_MSR_CRASH_CTL, HV_CRASH_CTL_CRASH_NOTIFY);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int hyperv_panic_event(struct notifier_block *nb, unsigned long val,
|
static int hyperv_panic_event(struct notifier_block *nb, unsigned long val,
|
||||||
void *args)
|
void *args)
|
||||||
|
@ -859,9 +835,10 @@ static void vmbus_onmessage_work(struct work_struct *work)
|
||||||
kfree(ctx);
|
kfree(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void hv_process_timer_expiration(struct hv_message *msg, int cpu)
|
static void hv_process_timer_expiration(struct hv_message *msg,
|
||||||
|
struct hv_per_cpu_context *hv_cpu)
|
||||||
{
|
{
|
||||||
struct clock_event_device *dev = hv_context.clk_evt[cpu];
|
struct clock_event_device *dev = hv_cpu->clk_evt;
|
||||||
|
|
||||||
if (dev->event_handler)
|
if (dev->event_handler)
|
||||||
dev->event_handler(dev);
|
dev->event_handler(dev);
|
||||||
|
@ -871,8 +848,8 @@ static void hv_process_timer_expiration(struct hv_message *msg, int cpu)
|
||||||
|
|
||||||
void vmbus_on_msg_dpc(unsigned long data)
|
void vmbus_on_msg_dpc(unsigned long data)
|
||||||
{
|
{
|
||||||
int cpu = smp_processor_id();
|
struct hv_per_cpu_context *hv_cpu = (void *)data;
|
||||||
void *page_addr = hv_context.synic_message_page[cpu];
|
void *page_addr = hv_cpu->synic_message_page;
|
||||||
struct hv_message *msg = (struct hv_message *)page_addr +
|
struct hv_message *msg = (struct hv_message *)page_addr +
|
||||||
VMBUS_MESSAGE_SINT;
|
VMBUS_MESSAGE_SINT;
|
||||||
struct vmbus_channel_message_header *hdr;
|
struct vmbus_channel_message_header *hdr;
|
||||||
|
@ -908,16 +885,88 @@ void vmbus_on_msg_dpc(unsigned long data)
|
||||||
vmbus_signal_eom(msg, message_type);
|
vmbus_signal_eom(msg, message_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Direct callback for channels using other deferred processing
|
||||||
|
*/
|
||||||
|
static void vmbus_channel_isr(struct vmbus_channel *channel)
|
||||||
|
{
|
||||||
|
void (*callback_fn)(void *);
|
||||||
|
|
||||||
|
callback_fn = READ_ONCE(channel->onchannel_callback);
|
||||||
|
if (likely(callback_fn != NULL))
|
||||||
|
(*callback_fn)(channel->channel_callback_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Schedule all channels with events pending
|
||||||
|
*/
|
||||||
|
static void vmbus_chan_sched(struct hv_per_cpu_context *hv_cpu)
|
||||||
|
{
|
||||||
|
unsigned long *recv_int_page;
|
||||||
|
u32 maxbits, relid;
|
||||||
|
|
||||||
|
if (vmbus_proto_version < VERSION_WIN8) {
|
||||||
|
maxbits = MAX_NUM_CHANNELS_SUPPORTED;
|
||||||
|
recv_int_page = vmbus_connection.recv_int_page;
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* When the host is win8 and beyond, the event page
|
||||||
|
* can be directly checked to get the id of the channel
|
||||||
|
* that has the interrupt pending.
|
||||||
|
*/
|
||||||
|
void *page_addr = hv_cpu->synic_event_page;
|
||||||
|
union hv_synic_event_flags *event
|
||||||
|
= (union hv_synic_event_flags *)page_addr +
|
||||||
|
VMBUS_MESSAGE_SINT;
|
||||||
|
|
||||||
|
maxbits = HV_EVENT_FLAGS_COUNT;
|
||||||
|
recv_int_page = event->flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unlikely(!recv_int_page))
|
||||||
|
return;
|
||||||
|
|
||||||
|
for_each_set_bit(relid, recv_int_page, maxbits) {
|
||||||
|
struct vmbus_channel *channel;
|
||||||
|
|
||||||
|
if (!sync_test_and_clear_bit(relid, recv_int_page))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* Special case - vmbus channel protocol msg */
|
||||||
|
if (relid == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* Find channel based on relid */
|
||||||
|
list_for_each_entry(channel, &hv_cpu->chan_list, percpu_list) {
|
||||||
|
if (channel->offermsg.child_relid != relid)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
switch (channel->callback_mode) {
|
||||||
|
case HV_CALL_ISR:
|
||||||
|
vmbus_channel_isr(channel);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HV_CALL_BATCHED:
|
||||||
|
hv_begin_read(&channel->inbound);
|
||||||
|
/* fallthrough */
|
||||||
|
case HV_CALL_DIRECT:
|
||||||
|
tasklet_schedule(&channel->callback_event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void vmbus_isr(void)
|
static void vmbus_isr(void)
|
||||||
{
|
{
|
||||||
int cpu = smp_processor_id();
|
struct hv_per_cpu_context *hv_cpu
|
||||||
void *page_addr;
|
= this_cpu_ptr(hv_context.cpu_context);
|
||||||
|
void *page_addr = hv_cpu->synic_event_page;
|
||||||
struct hv_message *msg;
|
struct hv_message *msg;
|
||||||
union hv_synic_event_flags *event;
|
union hv_synic_event_flags *event;
|
||||||
bool handled = false;
|
bool handled = false;
|
||||||
|
|
||||||
page_addr = hv_context.synic_event_page[cpu];
|
if (unlikely(page_addr == NULL))
|
||||||
if (page_addr == NULL)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
event = (union hv_synic_event_flags *)page_addr +
|
event = (union hv_synic_event_flags *)page_addr +
|
||||||
|
@ -932,10 +981,8 @@ static void vmbus_isr(void)
|
||||||
(vmbus_proto_version == VERSION_WIN7)) {
|
(vmbus_proto_version == VERSION_WIN7)) {
|
||||||
|
|
||||||
/* Since we are a child, we only need to check bit 0 */
|
/* Since we are a child, we only need to check bit 0 */
|
||||||
if (sync_test_and_clear_bit(0,
|
if (sync_test_and_clear_bit(0, event->flags))
|
||||||
(unsigned long *) &event->flags32[0])) {
|
|
||||||
handled = true;
|
handled = true;
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
/*
|
/*
|
||||||
* Our host is win8 or above. The signaling mechanism
|
* Our host is win8 or above. The signaling mechanism
|
||||||
|
@ -947,18 +994,17 @@ static void vmbus_isr(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (handled)
|
if (handled)
|
||||||
tasklet_schedule(hv_context.event_dpc[cpu]);
|
vmbus_chan_sched(hv_cpu);
|
||||||
|
|
||||||
|
page_addr = hv_cpu->synic_message_page;
|
||||||
page_addr = hv_context.synic_message_page[cpu];
|
|
||||||
msg = (struct hv_message *)page_addr + VMBUS_MESSAGE_SINT;
|
msg = (struct hv_message *)page_addr + VMBUS_MESSAGE_SINT;
|
||||||
|
|
||||||
/* Check if there are actual msgs to be processed */
|
/* Check if there are actual msgs to be processed */
|
||||||
if (msg->header.message_type != HVMSG_NONE) {
|
if (msg->header.message_type != HVMSG_NONE) {
|
||||||
if (msg->header.message_type == HVMSG_TIMER_EXPIRED)
|
if (msg->header.message_type == HVMSG_TIMER_EXPIRED)
|
||||||
hv_process_timer_expiration(msg, cpu);
|
hv_process_timer_expiration(msg, hv_cpu);
|
||||||
else
|
else
|
||||||
tasklet_schedule(hv_context.msg_dpc[cpu]);
|
tasklet_schedule(&hv_cpu->msg_dpc);
|
||||||
}
|
}
|
||||||
|
|
||||||
add_interrupt_randomness(HYPERVISOR_CALLBACK_VECTOR, 0);
|
add_interrupt_randomness(HYPERVISOR_CALLBACK_VECTOR, 0);
|
||||||
|
@ -986,7 +1032,7 @@ static int vmbus_bus_init(void)
|
||||||
|
|
||||||
ret = bus_register(&hv_bus);
|
ret = bus_register(&hv_bus);
|
||||||
if (ret)
|
if (ret)
|
||||||
goto err_cleanup;
|
return ret;
|
||||||
|
|
||||||
hv_setup_vmbus_irq(vmbus_isr);
|
hv_setup_vmbus_irq(vmbus_isr);
|
||||||
|
|
||||||
|
@ -997,14 +1043,16 @@ static int vmbus_bus_init(void)
|
||||||
* Initialize the per-cpu interrupt state and
|
* Initialize the per-cpu interrupt state and
|
||||||
* connect to the host.
|
* connect to the host.
|
||||||
*/
|
*/
|
||||||
on_each_cpu(hv_synic_init, NULL, 1);
|
ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "x86/hyperv:online",
|
||||||
|
hv_synic_init, hv_synic_cleanup);
|
||||||
|
if (ret < 0)
|
||||||
|
goto err_alloc;
|
||||||
|
hyperv_cpuhp_online = ret;
|
||||||
|
|
||||||
ret = vmbus_connect();
|
ret = vmbus_connect();
|
||||||
if (ret)
|
if (ret)
|
||||||
goto err_connect;
|
goto err_connect;
|
||||||
|
|
||||||
if (vmbus_proto_version > VERSION_WIN7)
|
|
||||||
cpu_hotplug_disable();
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Only register if the crash MSRs are available
|
* Only register if the crash MSRs are available
|
||||||
*/
|
*/
|
||||||
|
@ -1019,16 +1067,13 @@ static int vmbus_bus_init(void)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
err_connect:
|
err_connect:
|
||||||
on_each_cpu(hv_synic_cleanup, NULL, 1);
|
cpuhp_remove_state(hyperv_cpuhp_online);
|
||||||
err_alloc:
|
err_alloc:
|
||||||
hv_synic_free();
|
hv_synic_free();
|
||||||
hv_remove_vmbus_irq();
|
hv_remove_vmbus_irq();
|
||||||
|
|
||||||
bus_unregister(&hv_bus);
|
bus_unregister(&hv_bus);
|
||||||
|
|
||||||
err_cleanup:
|
|
||||||
hv_cleanup(false);
|
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1478,13 +1523,13 @@ static struct acpi_driver vmbus_acpi_driver = {
|
||||||
|
|
||||||
static void hv_kexec_handler(void)
|
static void hv_kexec_handler(void)
|
||||||
{
|
{
|
||||||
int cpu;
|
|
||||||
|
|
||||||
hv_synic_clockevents_cleanup();
|
hv_synic_clockevents_cleanup();
|
||||||
vmbus_initiate_unload(false);
|
vmbus_initiate_unload(false);
|
||||||
for_each_online_cpu(cpu)
|
vmbus_connection.conn_state = DISCONNECTED;
|
||||||
smp_call_function_single(cpu, hv_synic_cleanup, NULL, 1);
|
/* Make sure conn_state is set as hv_synic_cleanup checks for it */
|
||||||
hv_cleanup(false);
|
mb();
|
||||||
|
cpuhp_remove_state(hyperv_cpuhp_online);
|
||||||
|
hyperv_cleanup();
|
||||||
};
|
};
|
||||||
|
|
||||||
static void hv_crash_handler(struct pt_regs *regs)
|
static void hv_crash_handler(struct pt_regs *regs)
|
||||||
|
@ -1495,8 +1540,9 @@ static void hv_crash_handler(struct pt_regs *regs)
|
||||||
* doing the cleanup for current CPU only. This should be sufficient
|
* doing the cleanup for current CPU only. This should be sufficient
|
||||||
* for kdump.
|
* for kdump.
|
||||||
*/
|
*/
|
||||||
hv_synic_cleanup(NULL);
|
vmbus_connection.conn_state = DISCONNECTED;
|
||||||
hv_cleanup(true);
|
hv_synic_cleanup(smp_processor_id());
|
||||||
|
hyperv_cleanup();
|
||||||
};
|
};
|
||||||
|
|
||||||
static int __init hv_acpi_init(void)
|
static int __init hv_acpi_init(void)
|
||||||
|
@ -1547,24 +1593,24 @@ static void __exit vmbus_exit(void)
|
||||||
hv_synic_clockevents_cleanup();
|
hv_synic_clockevents_cleanup();
|
||||||
vmbus_disconnect();
|
vmbus_disconnect();
|
||||||
hv_remove_vmbus_irq();
|
hv_remove_vmbus_irq();
|
||||||
for_each_online_cpu(cpu)
|
for_each_online_cpu(cpu) {
|
||||||
tasklet_kill(hv_context.msg_dpc[cpu]);
|
struct hv_per_cpu_context *hv_cpu
|
||||||
|
= per_cpu_ptr(hv_context.cpu_context, cpu);
|
||||||
|
|
||||||
|
tasklet_kill(&hv_cpu->msg_dpc);
|
||||||
|
}
|
||||||
vmbus_free_channels();
|
vmbus_free_channels();
|
||||||
|
|
||||||
if (ms_hyperv.misc_features & HV_FEATURE_GUEST_CRASH_MSR_AVAILABLE) {
|
if (ms_hyperv.misc_features & HV_FEATURE_GUEST_CRASH_MSR_AVAILABLE) {
|
||||||
unregister_die_notifier(&hyperv_die_block);
|
unregister_die_notifier(&hyperv_die_block);
|
||||||
atomic_notifier_chain_unregister(&panic_notifier_list,
|
atomic_notifier_chain_unregister(&panic_notifier_list,
|
||||||
&hyperv_panic_block);
|
&hyperv_panic_block);
|
||||||
}
|
}
|
||||||
bus_unregister(&hv_bus);
|
bus_unregister(&hv_bus);
|
||||||
hv_cleanup(false);
|
|
||||||
for_each_online_cpu(cpu) {
|
cpuhp_remove_state(hyperv_cpuhp_online);
|
||||||
tasklet_kill(hv_context.event_dpc[cpu]);
|
|
||||||
smp_call_function_single(cpu, hv_synic_cleanup, NULL, 1);
|
|
||||||
}
|
|
||||||
hv_synic_free();
|
hv_synic_free();
|
||||||
acpi_bus_unregister_driver(&vmbus_acpi_driver);
|
acpi_bus_unregister_driver(&vmbus_acpi_driver);
|
||||||
if (vmbus_proto_version > VERSION_WIN7)
|
|
||||||
cpu_hotplug_enable();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -242,6 +242,7 @@ static void *etm_setup_aux(int event_cpu, void **pages,
|
||||||
if (!sink_ops(sink)->alloc_buffer)
|
if (!sink_ops(sink)->alloc_buffer)
|
||||||
goto err;
|
goto err;
|
||||||
|
|
||||||
|
cpu = cpumask_first(mask);
|
||||||
/* Get the AUX specific data from the sink buffer */
|
/* Get the AUX specific data from the sink buffer */
|
||||||
event_data->snk_config =
|
event_data->snk_config =
|
||||||
sink_ops(sink)->alloc_buffer(sink, cpu, pages,
|
sink_ops(sink)->alloc_buffer(sink, cpu, pages,
|
||||||
|
|
|
@ -216,10 +216,14 @@ static int etm4_parse_event_config(struct etmv4_drvdata *drvdata,
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
/* Go from generic option to ETMv4 specifics */
|
/* Go from generic option to ETMv4 specifics */
|
||||||
if (attr->config & BIT(ETM_OPT_CYCACC))
|
if (attr->config & BIT(ETM_OPT_CYCACC)) {
|
||||||
config->cfg |= ETMv4_MODE_CYCACC;
|
config->cfg |= BIT(4);
|
||||||
|
/* TRM: Must program this for cycacc to work */
|
||||||
|
config->ccctlr = ETM_CYC_THRESHOLD_DEFAULT;
|
||||||
|
}
|
||||||
if (attr->config & BIT(ETM_OPT_TS))
|
if (attr->config & BIT(ETM_OPT_TS))
|
||||||
config->cfg |= ETMv4_MODE_TIMESTAMP;
|
/* bit[11], Global timestamp tracing bit */
|
||||||
|
config->cfg |= BIT(11);
|
||||||
|
|
||||||
out:
|
out:
|
||||||
return ret;
|
return ret;
|
||||||
|
|
|
@ -146,6 +146,7 @@
|
||||||
#define ETM_ARCH_V4 0x40
|
#define ETM_ARCH_V4 0x40
|
||||||
#define ETMv4_SYNC_MASK 0x1F
|
#define ETMv4_SYNC_MASK 0x1F
|
||||||
#define ETM_CYC_THRESHOLD_MASK 0xFFF
|
#define ETM_CYC_THRESHOLD_MASK 0xFFF
|
||||||
|
#define ETM_CYC_THRESHOLD_DEFAULT 0x100
|
||||||
#define ETMv4_EVENT_MASK 0xFF
|
#define ETMv4_EVENT_MASK 0xFF
|
||||||
#define ETM_CNTR_MAX_VAL 0xFFFF
|
#define ETM_CNTR_MAX_VAL 0xFFFF
|
||||||
#define ETM_TRACEID_MASK 0x3f
|
#define ETM_TRACEID_MASK 0x3f
|
||||||
|
|
|
@ -356,7 +356,7 @@ static void stm_generic_unlink(struct stm_data *stm_data,
|
||||||
if (!drvdata || !drvdata->csdev)
|
if (!drvdata || !drvdata->csdev)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
stm_disable(drvdata->csdev, NULL);
|
coresight_disable(drvdata->csdev);
|
||||||
}
|
}
|
||||||
|
|
||||||
static phys_addr_t
|
static phys_addr_t
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
#include <linux/of.h>
|
#include <linux/of.h>
|
||||||
#include <linux/of_platform.h>
|
#include <linux/of_platform.h>
|
||||||
#include <linux/platform_device.h>
|
#include <linux/platform_device.h>
|
||||||
|
#include <linux/platform_data/ti-aemif.h>
|
||||||
|
|
||||||
#define TA_SHIFT 2
|
#define TA_SHIFT 2
|
||||||
#define RHOLD_SHIFT 4
|
#define RHOLD_SHIFT 4
|
||||||
|
@ -335,6 +336,8 @@ static int aemif_probe(struct platform_device *pdev)
|
||||||
struct device_node *np = dev->of_node;
|
struct device_node *np = dev->of_node;
|
||||||
struct device_node *child_np;
|
struct device_node *child_np;
|
||||||
struct aemif_device *aemif;
|
struct aemif_device *aemif;
|
||||||
|
struct aemif_platform_data *pdata;
|
||||||
|
struct of_dev_auxdata *dev_lookup;
|
||||||
|
|
||||||
if (np == NULL)
|
if (np == NULL)
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -343,6 +346,9 @@ static int aemif_probe(struct platform_device *pdev)
|
||||||
if (!aemif)
|
if (!aemif)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
|
||||||
|
pdata = dev_get_platdata(&pdev->dev);
|
||||||
|
dev_lookup = pdata ? pdata->dev_lookup : NULL;
|
||||||
|
|
||||||
platform_set_drvdata(pdev, aemif);
|
platform_set_drvdata(pdev, aemif);
|
||||||
|
|
||||||
aemif->clk = devm_clk_get(dev, NULL);
|
aemif->clk = devm_clk_get(dev, NULL);
|
||||||
|
@ -390,7 +396,7 @@ static int aemif_probe(struct platform_device *pdev)
|
||||||
* parameters are set.
|
* parameters are set.
|
||||||
*/
|
*/
|
||||||
for_each_available_child_of_node(np, child_np) {
|
for_each_available_child_of_node(np, child_np) {
|
||||||
ret = of_platform_populate(child_np, NULL, NULL, dev);
|
ret = of_platform_populate(child_np, NULL, dev_lookup, dev);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
|
@ -474,11 +474,15 @@ config SRAM
|
||||||
bool "Generic on-chip SRAM driver"
|
bool "Generic on-chip SRAM driver"
|
||||||
depends on HAS_IOMEM
|
depends on HAS_IOMEM
|
||||||
select GENERIC_ALLOCATOR
|
select GENERIC_ALLOCATOR
|
||||||
|
select SRAM_EXEC if ARM
|
||||||
help
|
help
|
||||||
This driver allows you to declare a memory region to be managed by
|
This driver allows you to declare a memory region to be managed by
|
||||||
the genalloc API. It is supposed to be used for small on-chip SRAM
|
the genalloc API. It is supposed to be used for small on-chip SRAM
|
||||||
areas found on many SoCs.
|
areas found on many SoCs.
|
||||||
|
|
||||||
|
config SRAM_EXEC
|
||||||
|
bool
|
||||||
|
|
||||||
config VEXPRESS_SYSCFG
|
config VEXPRESS_SYSCFG
|
||||||
bool "Versatile Express System Configuration driver"
|
bool "Versatile Express System Configuration driver"
|
||||||
depends on VEXPRESS_CONFIG
|
depends on VEXPRESS_CONFIG
|
||||||
|
@ -487,6 +491,7 @@ config VEXPRESS_SYSCFG
|
||||||
ARM Ltd. Versatile Express uses specialised platform configuration
|
ARM Ltd. Versatile Express uses specialised platform configuration
|
||||||
bus. System Configuration interface is one of the possible means
|
bus. System Configuration interface is one of the possible means
|
||||||
of generating transactions on this bus.
|
of generating transactions on this bus.
|
||||||
|
|
||||||
config PANEL
|
config PANEL
|
||||||
tristate "Parallel port LCD/Keypad Panel support"
|
tristate "Parallel port LCD/Keypad Panel support"
|
||||||
depends on PARPORT
|
depends on PARPORT
|
||||||
|
@ -494,14 +499,14 @@ config PANEL
|
||||||
Say Y here if you have an HD44780 or KS-0074 LCD connected to your
|
Say Y here if you have an HD44780 or KS-0074 LCD connected to your
|
||||||
parallel port. This driver also features 4 and 6-key keypads. The LCD
|
parallel port. This driver also features 4 and 6-key keypads. The LCD
|
||||||
is accessible through the /dev/lcd char device (10, 156), and the
|
is accessible through the /dev/lcd char device (10, 156), and the
|
||||||
keypad through /dev/keypad (10, 185). Both require misc device to be
|
keypad through /dev/keypad (10, 185). This code can either be
|
||||||
enabled. This code can either be compiled as a module, or linked into
|
compiled as a module, or linked into the kernel and started at boot.
|
||||||
the kernel and started at boot. If you don't understand what all this
|
If you don't understand what all this is about, say N.
|
||||||
is about, say N.
|
|
||||||
|
if PANEL
|
||||||
|
|
||||||
config PANEL_PARPORT
|
config PANEL_PARPORT
|
||||||
int "Default parallel port number (0=LPT1)"
|
int "Default parallel port number (0=LPT1)"
|
||||||
depends on PANEL
|
|
||||||
range 0 255
|
range 0 255
|
||||||
default "0"
|
default "0"
|
||||||
---help---
|
---help---
|
||||||
|
@ -513,7 +518,6 @@ config PANEL_PARPORT
|
||||||
|
|
||||||
config PANEL_PROFILE
|
config PANEL_PROFILE
|
||||||
int "Default panel profile (0-5, 0=custom)"
|
int "Default panel profile (0-5, 0=custom)"
|
||||||
depends on PANEL
|
|
||||||
range 0 5
|
range 0 5
|
||||||
default "5"
|
default "5"
|
||||||
---help---
|
---help---
|
||||||
|
@ -534,7 +538,7 @@ config PANEL_PROFILE
|
||||||
for experts.
|
for experts.
|
||||||
|
|
||||||
config PANEL_KEYPAD
|
config PANEL_KEYPAD
|
||||||
depends on PANEL && PANEL_PROFILE="0"
|
depends on PANEL_PROFILE="0"
|
||||||
int "Keypad type (0=none, 1=old 6 keys, 2=new 6 keys, 3=Nexcom 4 keys)"
|
int "Keypad type (0=none, 1=old 6 keys, 2=new 6 keys, 3=Nexcom 4 keys)"
|
||||||
range 0 3
|
range 0 3
|
||||||
default 0
|
default 0
|
||||||
|
@ -551,7 +555,7 @@ config PANEL_KEYPAD
|
||||||
supports simultaneous keys pressed when the keypad supports them.
|
supports simultaneous keys pressed when the keypad supports them.
|
||||||
|
|
||||||
config PANEL_LCD
|
config PANEL_LCD
|
||||||
depends on PANEL && PANEL_PROFILE="0"
|
depends on PANEL_PROFILE="0"
|
||||||
int "LCD type (0=none, 1=custom, 2=old //, 3=ks0074, 4=hantronix, 5=Nexcom)"
|
int "LCD type (0=none, 1=custom, 2=old //, 3=ks0074, 4=hantronix, 5=Nexcom)"
|
||||||
range 0 5
|
range 0 5
|
||||||
default 0
|
default 0
|
||||||
|
@ -574,7 +578,7 @@ config PANEL_LCD
|
||||||
that those values changed from the 2.4 driver for better consistency.
|
that those values changed from the 2.4 driver for better consistency.
|
||||||
|
|
||||||
config PANEL_LCD_HEIGHT
|
config PANEL_LCD_HEIGHT
|
||||||
depends on PANEL && PANEL_PROFILE="0" && PANEL_LCD="1"
|
depends on PANEL_PROFILE="0" && PANEL_LCD="1"
|
||||||
int "Number of lines on the LCD (1-2)"
|
int "Number of lines on the LCD (1-2)"
|
||||||
range 1 2
|
range 1 2
|
||||||
default 2
|
default 2
|
||||||
|
@ -583,7 +587,7 @@ config PANEL_LCD_HEIGHT
|
||||||
It can either be 1 or 2.
|
It can either be 1 or 2.
|
||||||
|
|
||||||
config PANEL_LCD_WIDTH
|
config PANEL_LCD_WIDTH
|
||||||
depends on PANEL && PANEL_PROFILE="0" && PANEL_LCD="1"
|
depends on PANEL_PROFILE="0" && PANEL_LCD="1"
|
||||||
int "Number of characters per line on the LCD (1-40)"
|
int "Number of characters per line on the LCD (1-40)"
|
||||||
range 1 40
|
range 1 40
|
||||||
default 40
|
default 40
|
||||||
|
@ -592,7 +596,7 @@ config PANEL_LCD_WIDTH
|
||||||
Common values are 16,20,24,40.
|
Common values are 16,20,24,40.
|
||||||
|
|
||||||
config PANEL_LCD_BWIDTH
|
config PANEL_LCD_BWIDTH
|
||||||
depends on PANEL && PANEL_PROFILE="0" && PANEL_LCD="1"
|
depends on PANEL_PROFILE="0" && PANEL_LCD="1"
|
||||||
int "Internal LCD line width (1-40, 40 by default)"
|
int "Internal LCD line width (1-40, 40 by default)"
|
||||||
range 1 40
|
range 1 40
|
||||||
default 40
|
default 40
|
||||||
|
@ -608,7 +612,7 @@ config PANEL_LCD_BWIDTH
|
||||||
If you don't know, put '40' here.
|
If you don't know, put '40' here.
|
||||||
|
|
||||||
config PANEL_LCD_HWIDTH
|
config PANEL_LCD_HWIDTH
|
||||||
depends on PANEL && PANEL_PROFILE="0" && PANEL_LCD="1"
|
depends on PANEL_PROFILE="0" && PANEL_LCD="1"
|
||||||
int "Hardware LCD line width (1-64, 64 by default)"
|
int "Hardware LCD line width (1-64, 64 by default)"
|
||||||
range 1 64
|
range 1 64
|
||||||
default 64
|
default 64
|
||||||
|
@ -622,7 +626,7 @@ config PANEL_LCD_HWIDTH
|
||||||
64 here for a 2x40.
|
64 here for a 2x40.
|
||||||
|
|
||||||
config PANEL_LCD_CHARSET
|
config PANEL_LCD_CHARSET
|
||||||
depends on PANEL && PANEL_PROFILE="0" && PANEL_LCD="1"
|
depends on PANEL_PROFILE="0" && PANEL_LCD="1"
|
||||||
int "LCD character set (0=normal, 1=KS0074)"
|
int "LCD character set (0=normal, 1=KS0074)"
|
||||||
range 0 1
|
range 0 1
|
||||||
default 0
|
default 0
|
||||||
|
@ -638,7 +642,7 @@ config PANEL_LCD_CHARSET
|
||||||
If you don't know, use the normal one (0).
|
If you don't know, use the normal one (0).
|
||||||
|
|
||||||
config PANEL_LCD_PROTO
|
config PANEL_LCD_PROTO
|
||||||
depends on PANEL && PANEL_PROFILE="0" && PANEL_LCD="1"
|
depends on PANEL_PROFILE="0" && PANEL_LCD="1"
|
||||||
int "LCD communication mode (0=parallel 8 bits, 1=serial)"
|
int "LCD communication mode (0=parallel 8 bits, 1=serial)"
|
||||||
range 0 1
|
range 0 1
|
||||||
default 0
|
default 0
|
||||||
|
@ -651,7 +655,7 @@ config PANEL_LCD_PROTO
|
||||||
parallel LCD, and 1 for a serial LCD.
|
parallel LCD, and 1 for a serial LCD.
|
||||||
|
|
||||||
config PANEL_LCD_PIN_E
|
config PANEL_LCD_PIN_E
|
||||||
depends on PANEL && PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO="0"
|
depends on PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO="0"
|
||||||
int "Parallel port pin number & polarity connected to the LCD E signal (-17...17) "
|
int "Parallel port pin number & polarity connected to the LCD E signal (-17...17) "
|
||||||
range -17 17
|
range -17 17
|
||||||
default 14
|
default 14
|
||||||
|
@ -666,7 +670,7 @@ config PANEL_LCD_PIN_E
|
||||||
Default for the 'E' pin in custom profile is '14' (AUTOFEED).
|
Default for the 'E' pin in custom profile is '14' (AUTOFEED).
|
||||||
|
|
||||||
config PANEL_LCD_PIN_RS
|
config PANEL_LCD_PIN_RS
|
||||||
depends on PANEL && PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO="0"
|
depends on PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO="0"
|
||||||
int "Parallel port pin number & polarity connected to the LCD RS signal (-17...17) "
|
int "Parallel port pin number & polarity connected to the LCD RS signal (-17...17) "
|
||||||
range -17 17
|
range -17 17
|
||||||
default 17
|
default 17
|
||||||
|
@ -681,7 +685,7 @@ config PANEL_LCD_PIN_RS
|
||||||
Default for the 'RS' pin in custom profile is '17' (SELECT IN).
|
Default for the 'RS' pin in custom profile is '17' (SELECT IN).
|
||||||
|
|
||||||
config PANEL_LCD_PIN_RW
|
config PANEL_LCD_PIN_RW
|
||||||
depends on PANEL && PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO="0"
|
depends on PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO="0"
|
||||||
int "Parallel port pin number & polarity connected to the LCD RW signal (-17...17) "
|
int "Parallel port pin number & polarity connected to the LCD RW signal (-17...17) "
|
||||||
range -17 17
|
range -17 17
|
||||||
default 16
|
default 16
|
||||||
|
@ -696,7 +700,7 @@ config PANEL_LCD_PIN_RW
|
||||||
Default for the 'RW' pin in custom profile is '16' (INIT).
|
Default for the 'RW' pin in custom profile is '16' (INIT).
|
||||||
|
|
||||||
config PANEL_LCD_PIN_SCL
|
config PANEL_LCD_PIN_SCL
|
||||||
depends on PANEL && PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO!="0"
|
depends on PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO!="0"
|
||||||
int "Parallel port pin number & polarity connected to the LCD SCL signal (-17...17) "
|
int "Parallel port pin number & polarity connected to the LCD SCL signal (-17...17) "
|
||||||
range -17 17
|
range -17 17
|
||||||
default 1
|
default 1
|
||||||
|
@ -711,7 +715,7 @@ config PANEL_LCD_PIN_SCL
|
||||||
Default for the 'SCL' pin in custom profile is '1' (STROBE).
|
Default for the 'SCL' pin in custom profile is '1' (STROBE).
|
||||||
|
|
||||||
config PANEL_LCD_PIN_SDA
|
config PANEL_LCD_PIN_SDA
|
||||||
depends on PANEL && PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO!="0"
|
depends on PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO!="0"
|
||||||
int "Parallel port pin number & polarity connected to the LCD SDA signal (-17...17) "
|
int "Parallel port pin number & polarity connected to the LCD SDA signal (-17...17) "
|
||||||
range -17 17
|
range -17 17
|
||||||
default 2
|
default 2
|
||||||
|
@ -726,7 +730,7 @@ config PANEL_LCD_PIN_SDA
|
||||||
Default for the 'SDA' pin in custom profile is '2' (D0).
|
Default for the 'SDA' pin in custom profile is '2' (D0).
|
||||||
|
|
||||||
config PANEL_LCD_PIN_BL
|
config PANEL_LCD_PIN_BL
|
||||||
depends on PANEL && PANEL_PROFILE="0" && PANEL_LCD="1"
|
depends on PANEL_PROFILE="0" && PANEL_LCD="1"
|
||||||
int "Parallel port pin number & polarity connected to the LCD backlight signal (-17...17) "
|
int "Parallel port pin number & polarity connected to the LCD backlight signal (-17...17) "
|
||||||
range -17 17
|
range -17 17
|
||||||
default 0
|
default 0
|
||||||
|
@ -741,7 +745,6 @@ config PANEL_LCD_PIN_BL
|
||||||
Default for the 'BL' pin in custom profile is '0' (uncontrolled).
|
Default for the 'BL' pin in custom profile is '0' (uncontrolled).
|
||||||
|
|
||||||
config PANEL_CHANGE_MESSAGE
|
config PANEL_CHANGE_MESSAGE
|
||||||
depends on PANEL
|
|
||||||
bool "Change LCD initialization message ?"
|
bool "Change LCD initialization message ?"
|
||||||
default "n"
|
default "n"
|
||||||
---help---
|
---help---
|
||||||
|
@ -754,7 +757,7 @@ config PANEL_CHANGE_MESSAGE
|
||||||
say 'N' and keep the default message with the version.
|
say 'N' and keep the default message with the version.
|
||||||
|
|
||||||
config PANEL_BOOT_MESSAGE
|
config PANEL_BOOT_MESSAGE
|
||||||
depends on PANEL && PANEL_CHANGE_MESSAGE="y"
|
depends on PANEL_CHANGE_MESSAGE="y"
|
||||||
string "New initialization message"
|
string "New initialization message"
|
||||||
default ""
|
default ""
|
||||||
---help---
|
---help---
|
||||||
|
@ -766,6 +769,8 @@ config PANEL_BOOT_MESSAGE
|
||||||
An empty message will only clear the display at driver init time. Any other
|
An empty message will only clear the display at driver init time. Any other
|
||||||
printf()-formatted message is valid with newline and escape codes.
|
printf()-formatted message is valid with newline and escape codes.
|
||||||
|
|
||||||
|
endif # PANEL
|
||||||
|
|
||||||
source "drivers/misc/c2port/Kconfig"
|
source "drivers/misc/c2port/Kconfig"
|
||||||
source "drivers/misc/eeprom/Kconfig"
|
source "drivers/misc/eeprom/Kconfig"
|
||||||
source "drivers/misc/cb710/Kconfig"
|
source "drivers/misc/cb710/Kconfig"
|
||||||
|
|
|
@ -47,6 +47,7 @@ obj-$(CONFIG_INTEL_MEI) += mei/
|
||||||
obj-$(CONFIG_VMWARE_VMCI) += vmw_vmci/
|
obj-$(CONFIG_VMWARE_VMCI) += vmw_vmci/
|
||||||
obj-$(CONFIG_LATTICE_ECP3_CONFIG) += lattice-ecp3-config.o
|
obj-$(CONFIG_LATTICE_ECP3_CONFIG) += lattice-ecp3-config.o
|
||||||
obj-$(CONFIG_SRAM) += sram.o
|
obj-$(CONFIG_SRAM) += sram.o
|
||||||
|
obj-$(CONFIG_SRAM_EXEC) += sram-exec.o
|
||||||
obj-y += mic/
|
obj-y += mic/
|
||||||
obj-$(CONFIG_GENWQE) += genwqe/
|
obj-$(CONFIG_GENWQE) += genwqe/
|
||||||
obj-$(CONFIG_ECHO) += echo/
|
obj-$(CONFIG_ECHO) += echo/
|
||||||
|
|
|
@ -100,4 +100,14 @@ config EEPROM_DIGSY_MTC_CFG
|
||||||
|
|
||||||
If unsure, say N.
|
If unsure, say N.
|
||||||
|
|
||||||
|
config EEPROM_IDT_89HPESX
|
||||||
|
tristate "IDT 89HPESx PCIe-swtiches EEPROM / CSR support"
|
||||||
|
depends on I2C && SYSFS
|
||||||
|
help
|
||||||
|
Enable this driver to get read/write access to EEPROM / CSRs
|
||||||
|
over IDT PCIe-swtich i2c-slave interface.
|
||||||
|
|
||||||
|
This driver can also be built as a module. If so, the module
|
||||||
|
will be called idt_89hpesx.
|
||||||
|
|
||||||
endmenu
|
endmenu
|
||||||
|
|
|
@ -5,3 +5,4 @@ obj-$(CONFIG_EEPROM_MAX6875) += max6875.o
|
||||||
obj-$(CONFIG_EEPROM_93CX6) += eeprom_93cx6.o
|
obj-$(CONFIG_EEPROM_93CX6) += eeprom_93cx6.o
|
||||||
obj-$(CONFIG_EEPROM_93XX46) += eeprom_93xx46.o
|
obj-$(CONFIG_EEPROM_93XX46) += eeprom_93xx46.o
|
||||||
obj-$(CONFIG_EEPROM_DIGSY_MTC_CFG) += digsy_mtc_eeprom.o
|
obj-$(CONFIG_EEPROM_DIGSY_MTC_CFG) += digsy_mtc_eeprom.o
|
||||||
|
obj-$(CONFIG_EEPROM_IDT_89HPESX) += idt_89hpesx.o
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1336,7 +1336,6 @@ static int genwqe_sriov_configure(struct pci_dev *dev, int numvfs)
|
||||||
static struct pci_error_handlers genwqe_err_handler = {
|
static struct pci_error_handlers genwqe_err_handler = {
|
||||||
.error_detected = genwqe_err_error_detected,
|
.error_detected = genwqe_err_error_detected,
|
||||||
.mmio_enabled = genwqe_err_result_none,
|
.mmio_enabled = genwqe_err_result_none,
|
||||||
.link_reset = genwqe_err_result_none,
|
|
||||||
.slot_reset = genwqe_err_slot_reset,
|
.slot_reset = genwqe_err_slot_reset,
|
||||||
.resume = genwqe_err_resume,
|
.resume = genwqe_err_resume,
|
||||||
};
|
};
|
||||||
|
|
|
@ -81,12 +81,17 @@ void lkdtm_OVERFLOW(void)
|
||||||
(void) recursive_loop(recur_count);
|
(void) recursive_loop(recur_count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static noinline void __lkdtm_CORRUPT_STACK(void *stack)
|
||||||
|
{
|
||||||
|
memset(stack, 'a', 64);
|
||||||
|
}
|
||||||
|
|
||||||
noinline void lkdtm_CORRUPT_STACK(void)
|
noinline void lkdtm_CORRUPT_STACK(void)
|
||||||
{
|
{
|
||||||
/* Use default char array length that triggers stack protection. */
|
/* Use default char array length that triggers stack protection. */
|
||||||
char data[8];
|
char data[8];
|
||||||
|
__lkdtm_CORRUPT_STACK(&data);
|
||||||
|
|
||||||
memset((void *)data, 'a', 64);
|
|
||||||
pr_info("Corrupted stack with '%16s'...\n", data);
|
pr_info("Corrupted stack with '%16s'...\n", data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -539,7 +539,9 @@ static void __exit lkdtm_module_exit(void)
|
||||||
/* Handle test-specific clean-up. */
|
/* Handle test-specific clean-up. */
|
||||||
lkdtm_usercopy_exit();
|
lkdtm_usercopy_exit();
|
||||||
|
|
||||||
|
if (lkdtm_jprobe != NULL)
|
||||||
unregister_jprobe(lkdtm_jprobe);
|
unregister_jprobe(lkdtm_jprobe);
|
||||||
|
|
||||||
pr_info("Crash point unregistered\n");
|
pr_info("Crash point unregistered\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -132,8 +132,7 @@ int mei_amthif_run_next_cmd(struct mei_device *dev)
|
||||||
|
|
||||||
dev_dbg(dev->dev, "complete amthif cmd_list cb.\n");
|
dev_dbg(dev->dev, "complete amthif cmd_list cb.\n");
|
||||||
|
|
||||||
cb = list_first_entry_or_null(&dev->amthif_cmd_list.list,
|
cb = list_first_entry_or_null(&dev->amthif_cmd_list, typeof(*cb), list);
|
||||||
typeof(*cb), list);
|
|
||||||
if (!cb) {
|
if (!cb) {
|
||||||
dev->iamthif_state = MEI_IAMTHIF_IDLE;
|
dev->iamthif_state = MEI_IAMTHIF_IDLE;
|
||||||
cl->fp = NULL;
|
cl->fp = NULL;
|
||||||
|
@ -167,7 +166,7 @@ int mei_amthif_write(struct mei_cl *cl, struct mei_cl_cb *cb)
|
||||||
|
|
||||||
struct mei_device *dev = cl->dev;
|
struct mei_device *dev = cl->dev;
|
||||||
|
|
||||||
list_add_tail(&cb->list, &dev->amthif_cmd_list.list);
|
list_add_tail(&cb->list, &dev->amthif_cmd_list);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The previous request is still in processing, queue this one.
|
* The previous request is still in processing, queue this one.
|
||||||
|
@ -211,7 +210,7 @@ unsigned int mei_amthif_poll(struct file *file, poll_table *wait)
|
||||||
* Return: 0, OK; otherwise, error.
|
* Return: 0, OK; otherwise, error.
|
||||||
*/
|
*/
|
||||||
int mei_amthif_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb,
|
int mei_amthif_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb,
|
||||||
struct mei_cl_cb *cmpl_list)
|
struct list_head *cmpl_list)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
|
@ -237,7 +236,7 @@ int mei_amthif_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb,
|
||||||
*/
|
*/
|
||||||
int mei_amthif_irq_read_msg(struct mei_cl *cl,
|
int mei_amthif_irq_read_msg(struct mei_cl *cl,
|
||||||
struct mei_msg_hdr *mei_hdr,
|
struct mei_msg_hdr *mei_hdr,
|
||||||
struct mei_cl_cb *cmpl_list)
|
struct list_head *cmpl_list)
|
||||||
{
|
{
|
||||||
struct mei_device *dev;
|
struct mei_device *dev;
|
||||||
int ret;
|
int ret;
|
||||||
|
@ -311,51 +310,31 @@ void mei_amthif_complete(struct mei_cl *cl, struct mei_cl_cb *cb)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* mei_clear_list - removes all callbacks associated with file
|
|
||||||
* from mei_cb_list
|
|
||||||
*
|
|
||||||
* @file: file structure
|
|
||||||
* @mei_cb_list: callbacks list
|
|
||||||
*
|
|
||||||
* mei_clear_list is called to clear resources associated with file
|
|
||||||
* when application calls close function or Ctrl-C was pressed
|
|
||||||
*/
|
|
||||||
static void mei_clear_list(const struct file *file,
|
|
||||||
struct list_head *mei_cb_list)
|
|
||||||
{
|
|
||||||
struct mei_cl_cb *cb, *next;
|
|
||||||
|
|
||||||
list_for_each_entry_safe(cb, next, mei_cb_list, list)
|
|
||||||
if (file == cb->fp)
|
|
||||||
mei_io_cb_free(cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* mei_amthif_release - the release function
|
* mei_amthif_release - the release function
|
||||||
*
|
*
|
||||||
* @dev: device structure
|
* @dev: device structure
|
||||||
* @file: pointer to file structure
|
* @fp: pointer to file structure
|
||||||
*
|
*
|
||||||
* Return: 0 on success, <0 on error
|
* Return: 0 on success, <0 on error
|
||||||
*/
|
*/
|
||||||
int mei_amthif_release(struct mei_device *dev, struct file *file)
|
int mei_amthif_release(struct mei_device *dev, struct file *fp)
|
||||||
{
|
{
|
||||||
struct mei_cl *cl = file->private_data;
|
struct mei_cl *cl = fp->private_data;
|
||||||
|
|
||||||
if (dev->iamthif_open_count > 0)
|
if (dev->iamthif_open_count > 0)
|
||||||
dev->iamthif_open_count--;
|
dev->iamthif_open_count--;
|
||||||
|
|
||||||
if (cl->fp == file && dev->iamthif_state != MEI_IAMTHIF_IDLE) {
|
if (cl->fp == fp && dev->iamthif_state != MEI_IAMTHIF_IDLE) {
|
||||||
|
|
||||||
dev_dbg(dev->dev, "amthif canceled iamthif state %d\n",
|
dev_dbg(dev->dev, "amthif canceled iamthif state %d\n",
|
||||||
dev->iamthif_state);
|
dev->iamthif_state);
|
||||||
dev->iamthif_canceled = true;
|
dev->iamthif_canceled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
mei_clear_list(file, &dev->amthif_cmd_list.list);
|
/* Don't clean ctrl_rd_list here, the reads has to be completed */
|
||||||
mei_clear_list(file, &cl->rd_completed);
|
mei_io_list_free_fp(&dev->amthif_cmd_list, fp);
|
||||||
mei_clear_list(file, &dev->ctrl_rd_list.list);
|
mei_io_list_free_fp(&cl->rd_completed, fp);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -498,6 +498,25 @@ int mei_cldev_enable(struct mei_cl_device *cldev)
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(mei_cldev_enable);
|
EXPORT_SYMBOL_GPL(mei_cldev_enable);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mei_cldev_unregister_callbacks - internal wrapper for unregistering
|
||||||
|
* callbacks.
|
||||||
|
*
|
||||||
|
* @cldev: client device
|
||||||
|
*/
|
||||||
|
static void mei_cldev_unregister_callbacks(struct mei_cl_device *cldev)
|
||||||
|
{
|
||||||
|
if (cldev->rx_cb) {
|
||||||
|
cancel_work_sync(&cldev->rx_work);
|
||||||
|
cldev->rx_cb = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cldev->notif_cb) {
|
||||||
|
cancel_work_sync(&cldev->notif_work);
|
||||||
|
cldev->notif_cb = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* mei_cldev_disable - disable me client device
|
* mei_cldev_disable - disable me client device
|
||||||
* disconnect form the me client
|
* disconnect form the me client
|
||||||
|
@ -519,6 +538,8 @@ int mei_cldev_disable(struct mei_cl_device *cldev)
|
||||||
|
|
||||||
bus = cldev->bus;
|
bus = cldev->bus;
|
||||||
|
|
||||||
|
mei_cldev_unregister_callbacks(cldev);
|
||||||
|
|
||||||
mutex_lock(&bus->device_lock);
|
mutex_lock(&bus->device_lock);
|
||||||
|
|
||||||
if (!mei_cl_is_connected(cl)) {
|
if (!mei_cl_is_connected(cl)) {
|
||||||
|
@ -541,6 +562,37 @@ int mei_cldev_disable(struct mei_cl_device *cldev)
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(mei_cldev_disable);
|
EXPORT_SYMBOL_GPL(mei_cldev_disable);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mei_cl_bus_module_get - acquire module of the underlying
|
||||||
|
* hw module.
|
||||||
|
*
|
||||||
|
* @cl: host client
|
||||||
|
*
|
||||||
|
* Return: true on success; false if the module was removed.
|
||||||
|
*/
|
||||||
|
bool mei_cl_bus_module_get(struct mei_cl *cl)
|
||||||
|
{
|
||||||
|
struct mei_cl_device *cldev = cl->cldev;
|
||||||
|
|
||||||
|
if (!cldev)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return try_module_get(cldev->bus->dev->driver->owner);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mei_cl_bus_module_put - release the underlying hw module.
|
||||||
|
*
|
||||||
|
* @cl: host client
|
||||||
|
*/
|
||||||
|
void mei_cl_bus_module_put(struct mei_cl *cl)
|
||||||
|
{
|
||||||
|
struct mei_cl_device *cldev = cl->cldev;
|
||||||
|
|
||||||
|
if (cldev)
|
||||||
|
module_put(cldev->bus->dev->driver->owner);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* mei_cl_device_find - find matching entry in the driver id table
|
* mei_cl_device_find - find matching entry in the driver id table
|
||||||
*
|
*
|
||||||
|
@ -665,19 +717,12 @@ static int mei_cl_device_remove(struct device *dev)
|
||||||
if (!cldev || !dev->driver)
|
if (!cldev || !dev->driver)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
if (cldev->rx_cb) {
|
|
||||||
cancel_work_sync(&cldev->rx_work);
|
|
||||||
cldev->rx_cb = NULL;
|
|
||||||
}
|
|
||||||
if (cldev->notif_cb) {
|
|
||||||
cancel_work_sync(&cldev->notif_work);
|
|
||||||
cldev->notif_cb = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
cldrv = to_mei_cl_driver(dev->driver);
|
cldrv = to_mei_cl_driver(dev->driver);
|
||||||
if (cldrv->remove)
|
if (cldrv->remove)
|
||||||
ret = cldrv->remove(cldev);
|
ret = cldrv->remove(cldev);
|
||||||
|
|
||||||
|
mei_cldev_unregister_callbacks(cldev);
|
||||||
|
|
||||||
module_put(THIS_MODULE);
|
module_put(THIS_MODULE);
|
||||||
dev->driver = NULL;
|
dev->driver = NULL;
|
||||||
return ret;
|
return ret;
|
||||||
|
|
|
@ -377,19 +377,19 @@ static struct mei_cl_cb *mei_io_cb_init(struct mei_cl *cl,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* __mei_io_list_flush - removes and frees cbs belonging to cl.
|
* __mei_io_list_flush_cl - removes and frees cbs belonging to cl.
|
||||||
*
|
*
|
||||||
* @list: an instance of our list structure
|
* @head: an instance of our list structure
|
||||||
* @cl: host client, can be NULL for flushing the whole list
|
* @cl: host client, can be NULL for flushing the whole list
|
||||||
* @free: whether to free the cbs
|
* @free: whether to free the cbs
|
||||||
*/
|
*/
|
||||||
static void __mei_io_list_flush(struct mei_cl_cb *list,
|
static void __mei_io_list_flush_cl(struct list_head *head,
|
||||||
struct mei_cl *cl, bool free)
|
const struct mei_cl *cl, bool free)
|
||||||
{
|
{
|
||||||
struct mei_cl_cb *cb, *next;
|
struct mei_cl_cb *cb, *next;
|
||||||
|
|
||||||
/* enable removing everything if no cl is specified */
|
/* enable removing everything if no cl is specified */
|
||||||
list_for_each_entry_safe(cb, next, &list->list, list) {
|
list_for_each_entry_safe(cb, next, head, list) {
|
||||||
if (!cl || mei_cl_cmp_id(cl, cb->cl)) {
|
if (!cl || mei_cl_cmp_id(cl, cb->cl)) {
|
||||||
list_del_init(&cb->list);
|
list_del_init(&cb->list);
|
||||||
if (free)
|
if (free)
|
||||||
|
@ -399,25 +399,42 @@ static void __mei_io_list_flush(struct mei_cl_cb *list,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* mei_io_list_flush - removes list entry belonging to cl.
|
* mei_io_list_flush_cl - removes list entry belonging to cl.
|
||||||
*
|
*
|
||||||
* @list: An instance of our list structure
|
* @head: An instance of our list structure
|
||||||
* @cl: host client
|
* @cl: host client
|
||||||
*/
|
*/
|
||||||
void mei_io_list_flush(struct mei_cl_cb *list, struct mei_cl *cl)
|
static inline void mei_io_list_flush_cl(struct list_head *head,
|
||||||
|
const struct mei_cl *cl)
|
||||||
{
|
{
|
||||||
__mei_io_list_flush(list, cl, false);
|
__mei_io_list_flush_cl(head, cl, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* mei_io_list_free - removes cb belonging to cl and free them
|
* mei_io_list_free_cl - removes cb belonging to cl and free them
|
||||||
*
|
*
|
||||||
* @list: An instance of our list structure
|
* @head: An instance of our list structure
|
||||||
* @cl: host client
|
* @cl: host client
|
||||||
*/
|
*/
|
||||||
static inline void mei_io_list_free(struct mei_cl_cb *list, struct mei_cl *cl)
|
static inline void mei_io_list_free_cl(struct list_head *head,
|
||||||
|
const struct mei_cl *cl)
|
||||||
{
|
{
|
||||||
__mei_io_list_flush(list, cl, true);
|
__mei_io_list_flush_cl(head, cl, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mei_io_list_free_fp - free cb from a list that matches file pointer
|
||||||
|
*
|
||||||
|
* @head: io list
|
||||||
|
* @fp: file pointer (matching cb file object), may be NULL
|
||||||
|
*/
|
||||||
|
void mei_io_list_free_fp(struct list_head *head, const struct file *fp)
|
||||||
|
{
|
||||||
|
struct mei_cl_cb *cb, *next;
|
||||||
|
|
||||||
|
list_for_each_entry_safe(cb, next, head, list)
|
||||||
|
if (!fp || fp == cb->fp)
|
||||||
|
mei_io_cb_free(cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -479,7 +496,7 @@ struct mei_cl_cb *mei_cl_enqueue_ctrl_wr_cb(struct mei_cl *cl, size_t length,
|
||||||
if (!cb)
|
if (!cb)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
list_add_tail(&cb->list, &cl->dev->ctrl_wr_list.list);
|
list_add_tail(&cb->list, &cl->dev->ctrl_wr_list);
|
||||||
return cb;
|
return cb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -503,27 +520,6 @@ struct mei_cl_cb *mei_cl_read_cb(const struct mei_cl *cl, const struct file *fp)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* mei_cl_read_cb_flush - free client's read pending and completed cbs
|
|
||||||
* for a specific file
|
|
||||||
*
|
|
||||||
* @cl: host client
|
|
||||||
* @fp: file pointer (matching cb file object), may be NULL
|
|
||||||
*/
|
|
||||||
void mei_cl_read_cb_flush(const struct mei_cl *cl, const struct file *fp)
|
|
||||||
{
|
|
||||||
struct mei_cl_cb *cb, *next;
|
|
||||||
|
|
||||||
list_for_each_entry_safe(cb, next, &cl->rd_completed, list)
|
|
||||||
if (!fp || fp == cb->fp)
|
|
||||||
mei_io_cb_free(cb);
|
|
||||||
|
|
||||||
|
|
||||||
list_for_each_entry_safe(cb, next, &cl->rd_pending, list)
|
|
||||||
if (!fp || fp == cb->fp)
|
|
||||||
mei_io_cb_free(cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* mei_cl_flush_queues - flushes queue lists belonging to cl.
|
* mei_cl_flush_queues - flushes queue lists belonging to cl.
|
||||||
*
|
*
|
||||||
|
@ -542,18 +538,16 @@ int mei_cl_flush_queues(struct mei_cl *cl, const struct file *fp)
|
||||||
dev = cl->dev;
|
dev = cl->dev;
|
||||||
|
|
||||||
cl_dbg(dev, cl, "remove list entry belonging to cl\n");
|
cl_dbg(dev, cl, "remove list entry belonging to cl\n");
|
||||||
mei_io_list_free(&cl->dev->write_list, cl);
|
mei_io_list_free_cl(&cl->dev->write_list, cl);
|
||||||
mei_io_list_free(&cl->dev->write_waiting_list, cl);
|
mei_io_list_free_cl(&cl->dev->write_waiting_list, cl);
|
||||||
mei_io_list_flush(&cl->dev->ctrl_wr_list, cl);
|
mei_io_list_flush_cl(&cl->dev->ctrl_wr_list, cl);
|
||||||
mei_io_list_flush(&cl->dev->ctrl_rd_list, cl);
|
mei_io_list_flush_cl(&cl->dev->ctrl_rd_list, cl);
|
||||||
mei_io_list_flush(&cl->dev->amthif_cmd_list, cl);
|
mei_io_list_free_fp(&cl->rd_pending, fp);
|
||||||
|
mei_io_list_free_fp(&cl->rd_completed, fp);
|
||||||
mei_cl_read_cb_flush(cl, fp);
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* mei_cl_init - initializes cl.
|
* mei_cl_init - initializes cl.
|
||||||
*
|
*
|
||||||
|
@ -756,7 +750,7 @@ static void mei_cl_wake_all(struct mei_cl *cl)
|
||||||
*
|
*
|
||||||
* @cl: host client
|
* @cl: host client
|
||||||
*/
|
*/
|
||||||
void mei_cl_set_disconnected(struct mei_cl *cl)
|
static void mei_cl_set_disconnected(struct mei_cl *cl)
|
||||||
{
|
{
|
||||||
struct mei_device *dev = cl->dev;
|
struct mei_device *dev = cl->dev;
|
||||||
|
|
||||||
|
@ -765,15 +759,18 @@ void mei_cl_set_disconnected(struct mei_cl *cl)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
cl->state = MEI_FILE_DISCONNECTED;
|
cl->state = MEI_FILE_DISCONNECTED;
|
||||||
mei_io_list_free(&dev->write_list, cl);
|
mei_io_list_free_cl(&dev->write_list, cl);
|
||||||
mei_io_list_free(&dev->write_waiting_list, cl);
|
mei_io_list_free_cl(&dev->write_waiting_list, cl);
|
||||||
mei_io_list_flush(&dev->ctrl_rd_list, cl);
|
mei_io_list_flush_cl(&dev->ctrl_rd_list, cl);
|
||||||
mei_io_list_flush(&dev->ctrl_wr_list, cl);
|
mei_io_list_flush_cl(&dev->ctrl_wr_list, cl);
|
||||||
|
mei_io_list_free_cl(&dev->amthif_cmd_list, cl);
|
||||||
mei_cl_wake_all(cl);
|
mei_cl_wake_all(cl);
|
||||||
cl->rx_flow_ctrl_creds = 0;
|
cl->rx_flow_ctrl_creds = 0;
|
||||||
cl->tx_flow_ctrl_creds = 0;
|
cl->tx_flow_ctrl_creds = 0;
|
||||||
cl->timer_count = 0;
|
cl->timer_count = 0;
|
||||||
|
|
||||||
|
mei_cl_bus_module_put(cl);
|
||||||
|
|
||||||
if (!cl->me_cl)
|
if (!cl->me_cl)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -829,7 +826,7 @@ static int mei_cl_send_disconnect(struct mei_cl *cl, struct mei_cl_cb *cb)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
list_move_tail(&cb->list, &dev->ctrl_rd_list.list);
|
list_move_tail(&cb->list, &dev->ctrl_rd_list);
|
||||||
cl->timer_count = MEI_CONNECT_TIMEOUT;
|
cl->timer_count = MEI_CONNECT_TIMEOUT;
|
||||||
mei_schedule_stall_timer(dev);
|
mei_schedule_stall_timer(dev);
|
||||||
|
|
||||||
|
@ -847,7 +844,7 @@ static int mei_cl_send_disconnect(struct mei_cl *cl, struct mei_cl_cb *cb)
|
||||||
* Return: 0, OK; otherwise, error.
|
* Return: 0, OK; otherwise, error.
|
||||||
*/
|
*/
|
||||||
int mei_cl_irq_disconnect(struct mei_cl *cl, struct mei_cl_cb *cb,
|
int mei_cl_irq_disconnect(struct mei_cl *cl, struct mei_cl_cb *cb,
|
||||||
struct mei_cl_cb *cmpl_list)
|
struct list_head *cmpl_list)
|
||||||
{
|
{
|
||||||
struct mei_device *dev = cl->dev;
|
struct mei_device *dev = cl->dev;
|
||||||
u32 msg_slots;
|
u32 msg_slots;
|
||||||
|
@ -862,7 +859,7 @@ int mei_cl_irq_disconnect(struct mei_cl *cl, struct mei_cl_cb *cb,
|
||||||
|
|
||||||
ret = mei_cl_send_disconnect(cl, cb);
|
ret = mei_cl_send_disconnect(cl, cb);
|
||||||
if (ret)
|
if (ret)
|
||||||
list_move_tail(&cb->list, &cmpl_list->list);
|
list_move_tail(&cb->list, cmpl_list);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -984,7 +981,7 @@ static bool mei_cl_is_other_connecting(struct mei_cl *cl)
|
||||||
|
|
||||||
dev = cl->dev;
|
dev = cl->dev;
|
||||||
|
|
||||||
list_for_each_entry(cb, &dev->ctrl_rd_list.list, list) {
|
list_for_each_entry(cb, &dev->ctrl_rd_list, list) {
|
||||||
if (cb->fop_type == MEI_FOP_CONNECT &&
|
if (cb->fop_type == MEI_FOP_CONNECT &&
|
||||||
mei_cl_me_id(cl) == mei_cl_me_id(cb->cl))
|
mei_cl_me_id(cl) == mei_cl_me_id(cb->cl))
|
||||||
return true;
|
return true;
|
||||||
|
@ -1015,7 +1012,7 @@ static int mei_cl_send_connect(struct mei_cl *cl, struct mei_cl_cb *cb)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
list_move_tail(&cb->list, &dev->ctrl_rd_list.list);
|
list_move_tail(&cb->list, &dev->ctrl_rd_list);
|
||||||
cl->timer_count = MEI_CONNECT_TIMEOUT;
|
cl->timer_count = MEI_CONNECT_TIMEOUT;
|
||||||
mei_schedule_stall_timer(dev);
|
mei_schedule_stall_timer(dev);
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -1031,7 +1028,7 @@ static int mei_cl_send_connect(struct mei_cl *cl, struct mei_cl_cb *cb)
|
||||||
* Return: 0, OK; otherwise, error.
|
* Return: 0, OK; otherwise, error.
|
||||||
*/
|
*/
|
||||||
int mei_cl_irq_connect(struct mei_cl *cl, struct mei_cl_cb *cb,
|
int mei_cl_irq_connect(struct mei_cl *cl, struct mei_cl_cb *cb,
|
||||||
struct mei_cl_cb *cmpl_list)
|
struct list_head *cmpl_list)
|
||||||
{
|
{
|
||||||
struct mei_device *dev = cl->dev;
|
struct mei_device *dev = cl->dev;
|
||||||
u32 msg_slots;
|
u32 msg_slots;
|
||||||
|
@ -1049,7 +1046,7 @@ int mei_cl_irq_connect(struct mei_cl *cl, struct mei_cl_cb *cb,
|
||||||
|
|
||||||
rets = mei_cl_send_connect(cl, cb);
|
rets = mei_cl_send_connect(cl, cb);
|
||||||
if (rets)
|
if (rets)
|
||||||
list_move_tail(&cb->list, &cmpl_list->list);
|
list_move_tail(&cb->list, cmpl_list);
|
||||||
|
|
||||||
return rets;
|
return rets;
|
||||||
}
|
}
|
||||||
|
@ -1077,13 +1074,17 @@ int mei_cl_connect(struct mei_cl *cl, struct mei_me_client *me_cl,
|
||||||
|
|
||||||
dev = cl->dev;
|
dev = cl->dev;
|
||||||
|
|
||||||
|
if (!mei_cl_bus_module_get(cl))
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
rets = mei_cl_set_connecting(cl, me_cl);
|
rets = mei_cl_set_connecting(cl, me_cl);
|
||||||
if (rets)
|
if (rets)
|
||||||
return rets;
|
goto nortpm;
|
||||||
|
|
||||||
if (mei_cl_is_fixed_address(cl)) {
|
if (mei_cl_is_fixed_address(cl)) {
|
||||||
cl->state = MEI_FILE_CONNECTED;
|
cl->state = MEI_FILE_CONNECTED;
|
||||||
return 0;
|
rets = 0;
|
||||||
|
goto nortpm;
|
||||||
}
|
}
|
||||||
|
|
||||||
rets = pm_runtime_get(dev->dev);
|
rets = pm_runtime_get(dev->dev);
|
||||||
|
@ -1117,8 +1118,8 @@ int mei_cl_connect(struct mei_cl *cl, struct mei_me_client *me_cl,
|
||||||
|
|
||||||
if (!mei_cl_is_connected(cl)) {
|
if (!mei_cl_is_connected(cl)) {
|
||||||
if (cl->state == MEI_FILE_DISCONNECT_REQUIRED) {
|
if (cl->state == MEI_FILE_DISCONNECT_REQUIRED) {
|
||||||
mei_io_list_flush(&dev->ctrl_rd_list, cl);
|
mei_io_list_flush_cl(&dev->ctrl_rd_list, cl);
|
||||||
mei_io_list_flush(&dev->ctrl_wr_list, cl);
|
mei_io_list_flush_cl(&dev->ctrl_wr_list, cl);
|
||||||
/* ignore disconnect return valuue;
|
/* ignore disconnect return valuue;
|
||||||
* in case of failure reset will be invoked
|
* in case of failure reset will be invoked
|
||||||
*/
|
*/
|
||||||
|
@ -1270,7 +1271,7 @@ enum mei_cb_file_ops mei_cl_notify_req2fop(u8 req)
|
||||||
* Return: 0 on such and error otherwise.
|
* Return: 0 on such and error otherwise.
|
||||||
*/
|
*/
|
||||||
int mei_cl_irq_notify(struct mei_cl *cl, struct mei_cl_cb *cb,
|
int mei_cl_irq_notify(struct mei_cl *cl, struct mei_cl_cb *cb,
|
||||||
struct mei_cl_cb *cmpl_list)
|
struct list_head *cmpl_list)
|
||||||
{
|
{
|
||||||
struct mei_device *dev = cl->dev;
|
struct mei_device *dev = cl->dev;
|
||||||
u32 msg_slots;
|
u32 msg_slots;
|
||||||
|
@ -1288,11 +1289,11 @@ int mei_cl_irq_notify(struct mei_cl *cl, struct mei_cl_cb *cb,
|
||||||
ret = mei_hbm_cl_notify_req(dev, cl, request);
|
ret = mei_hbm_cl_notify_req(dev, cl, request);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
cl->status = ret;
|
cl->status = ret;
|
||||||
list_move_tail(&cb->list, &cmpl_list->list);
|
list_move_tail(&cb->list, cmpl_list);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
list_move_tail(&cb->list, &dev->ctrl_rd_list.list);
|
list_move_tail(&cb->list, &dev->ctrl_rd_list);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1325,6 +1326,9 @@ int mei_cl_notify_request(struct mei_cl *cl,
|
||||||
return -EOPNOTSUPP;
|
return -EOPNOTSUPP;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!mei_cl_is_connected(cl))
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
rets = pm_runtime_get(dev->dev);
|
rets = pm_runtime_get(dev->dev);
|
||||||
if (rets < 0 && rets != -EINPROGRESS) {
|
if (rets < 0 && rets != -EINPROGRESS) {
|
||||||
pm_runtime_put_noidle(dev->dev);
|
pm_runtime_put_noidle(dev->dev);
|
||||||
|
@ -1344,7 +1348,7 @@ int mei_cl_notify_request(struct mei_cl *cl,
|
||||||
rets = -ENODEV;
|
rets = -ENODEV;
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
list_move_tail(&cb->list, &dev->ctrl_rd_list.list);
|
list_move_tail(&cb->list, &dev->ctrl_rd_list);
|
||||||
}
|
}
|
||||||
|
|
||||||
mutex_unlock(&dev->device_lock);
|
mutex_unlock(&dev->device_lock);
|
||||||
|
@ -1419,6 +1423,11 @@ int mei_cl_notify_get(struct mei_cl *cl, bool block, bool *notify_ev)
|
||||||
|
|
||||||
dev = cl->dev;
|
dev = cl->dev;
|
||||||
|
|
||||||
|
if (!dev->hbm_f_ev_supported) {
|
||||||
|
cl_dbg(dev, cl, "notifications not supported\n");
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
|
|
||||||
if (!mei_cl_is_connected(cl))
|
if (!mei_cl_is_connected(cl))
|
||||||
return -ENODEV;
|
return -ENODEV;
|
||||||
|
|
||||||
|
@ -1519,7 +1528,7 @@ int mei_cl_read_start(struct mei_cl *cl, size_t length, const struct file *fp)
|
||||||
* Return: 0, OK; otherwise error.
|
* Return: 0, OK; otherwise error.
|
||||||
*/
|
*/
|
||||||
int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb,
|
int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb,
|
||||||
struct mei_cl_cb *cmpl_list)
|
struct list_head *cmpl_list)
|
||||||
{
|
{
|
||||||
struct mei_device *dev;
|
struct mei_device *dev;
|
||||||
struct mei_msg_data *buf;
|
struct mei_msg_data *buf;
|
||||||
|
@ -1591,13 +1600,13 @@ int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mei_hdr.msg_complete)
|
if (mei_hdr.msg_complete)
|
||||||
list_move_tail(&cb->list, &dev->write_waiting_list.list);
|
list_move_tail(&cb->list, &dev->write_waiting_list);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
err:
|
err:
|
||||||
cl->status = rets;
|
cl->status = rets;
|
||||||
list_move_tail(&cb->list, &cmpl_list->list);
|
list_move_tail(&cb->list, cmpl_list);
|
||||||
return rets;
|
return rets;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1687,9 +1696,9 @@ int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb)
|
||||||
|
|
||||||
out:
|
out:
|
||||||
if (mei_hdr.msg_complete)
|
if (mei_hdr.msg_complete)
|
||||||
list_add_tail(&cb->list, &dev->write_waiting_list.list);
|
list_add_tail(&cb->list, &dev->write_waiting_list);
|
||||||
else
|
else
|
||||||
list_add_tail(&cb->list, &dev->write_list.list);
|
list_add_tail(&cb->list, &dev->write_list);
|
||||||
|
|
||||||
cb = NULL;
|
cb = NULL;
|
||||||
if (blocking && cl->writing_state != MEI_WRITE_COMPLETE) {
|
if (blocking && cl->writing_state != MEI_WRITE_COMPLETE) {
|
||||||
|
|
|
@ -83,17 +83,7 @@ static inline u8 mei_me_cl_ver(const struct mei_me_client *me_cl)
|
||||||
* MEI IO Functions
|
* MEI IO Functions
|
||||||
*/
|
*/
|
||||||
void mei_io_cb_free(struct mei_cl_cb *priv_cb);
|
void mei_io_cb_free(struct mei_cl_cb *priv_cb);
|
||||||
|
void mei_io_list_free_fp(struct list_head *head, const struct file *fp);
|
||||||
/**
|
|
||||||
* mei_io_list_init - Sets up a queue list.
|
|
||||||
*
|
|
||||||
* @list: An instance cl callback structure
|
|
||||||
*/
|
|
||||||
static inline void mei_io_list_init(struct mei_cl_cb *list)
|
|
||||||
{
|
|
||||||
INIT_LIST_HEAD(&list->list);
|
|
||||||
}
|
|
||||||
void mei_io_list_flush(struct mei_cl_cb *list, struct mei_cl *cl);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* MEI Host Client Functions
|
* MEI Host Client Functions
|
||||||
|
@ -110,7 +100,6 @@ struct mei_cl *mei_cl_alloc_linked(struct mei_device *dev);
|
||||||
|
|
||||||
struct mei_cl_cb *mei_cl_read_cb(const struct mei_cl *cl,
|
struct mei_cl_cb *mei_cl_read_cb(const struct mei_cl *cl,
|
||||||
const struct file *fp);
|
const struct file *fp);
|
||||||
void mei_cl_read_cb_flush(const struct mei_cl *cl, const struct file *fp);
|
|
||||||
struct mei_cl_cb *mei_cl_alloc_cb(struct mei_cl *cl, size_t length,
|
struct mei_cl_cb *mei_cl_alloc_cb(struct mei_cl *cl, size_t length,
|
||||||
enum mei_cb_file_ops type,
|
enum mei_cb_file_ops type,
|
||||||
const struct file *fp);
|
const struct file *fp);
|
||||||
|
@ -209,19 +198,18 @@ static inline u8 mei_cl_host_addr(const struct mei_cl *cl)
|
||||||
}
|
}
|
||||||
|
|
||||||
int mei_cl_disconnect(struct mei_cl *cl);
|
int mei_cl_disconnect(struct mei_cl *cl);
|
||||||
void mei_cl_set_disconnected(struct mei_cl *cl);
|
|
||||||
int mei_cl_irq_disconnect(struct mei_cl *cl, struct mei_cl_cb *cb,
|
int mei_cl_irq_disconnect(struct mei_cl *cl, struct mei_cl_cb *cb,
|
||||||
struct mei_cl_cb *cmpl_list);
|
struct list_head *cmpl_list);
|
||||||
int mei_cl_connect(struct mei_cl *cl, struct mei_me_client *me_cl,
|
int mei_cl_connect(struct mei_cl *cl, struct mei_me_client *me_cl,
|
||||||
const struct file *file);
|
const struct file *file);
|
||||||
int mei_cl_irq_connect(struct mei_cl *cl, struct mei_cl_cb *cb,
|
int mei_cl_irq_connect(struct mei_cl *cl, struct mei_cl_cb *cb,
|
||||||
struct mei_cl_cb *cmpl_list);
|
struct list_head *cmpl_list);
|
||||||
int mei_cl_read_start(struct mei_cl *cl, size_t length, const struct file *fp);
|
int mei_cl_read_start(struct mei_cl *cl, size_t length, const struct file *fp);
|
||||||
int mei_cl_irq_read_msg(struct mei_cl *cl, struct mei_msg_hdr *hdr,
|
int mei_cl_irq_read_msg(struct mei_cl *cl, struct mei_msg_hdr *hdr,
|
||||||
struct mei_cl_cb *cmpl_list);
|
struct list_head *cmpl_list);
|
||||||
int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb);
|
int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb);
|
||||||
int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb,
|
int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb,
|
||||||
struct mei_cl_cb *cmpl_list);
|
struct list_head *cmpl_list);
|
||||||
|
|
||||||
void mei_cl_complete(struct mei_cl *cl, struct mei_cl_cb *cb);
|
void mei_cl_complete(struct mei_cl *cl, struct mei_cl_cb *cb);
|
||||||
|
|
||||||
|
@ -232,7 +220,7 @@ enum mei_cb_file_ops mei_cl_notify_req2fop(u8 request);
|
||||||
int mei_cl_notify_request(struct mei_cl *cl,
|
int mei_cl_notify_request(struct mei_cl *cl,
|
||||||
const struct file *file, u8 request);
|
const struct file *file, u8 request);
|
||||||
int mei_cl_irq_notify(struct mei_cl *cl, struct mei_cl_cb *cb,
|
int mei_cl_irq_notify(struct mei_cl *cl, struct mei_cl_cb *cb,
|
||||||
struct mei_cl_cb *cmpl_list);
|
struct list_head *cmpl_list);
|
||||||
int mei_cl_notify_get(struct mei_cl *cl, bool block, bool *notify_ev);
|
int mei_cl_notify_get(struct mei_cl *cl, bool block, bool *notify_ev);
|
||||||
void mei_cl_notify(struct mei_cl *cl);
|
void mei_cl_notify(struct mei_cl *cl);
|
||||||
|
|
||||||
|
|
|
@ -815,7 +815,7 @@ static void mei_hbm_cl_res(struct mei_device *dev,
|
||||||
struct mei_cl_cb *cb, *next;
|
struct mei_cl_cb *cb, *next;
|
||||||
|
|
||||||
cl = NULL;
|
cl = NULL;
|
||||||
list_for_each_entry_safe(cb, next, &dev->ctrl_rd_list.list, list) {
|
list_for_each_entry_safe(cb, next, &dev->ctrl_rd_list, list) {
|
||||||
|
|
||||||
cl = cb->cl;
|
cl = cb->cl;
|
||||||
|
|
||||||
|
|
|
@ -139,6 +139,19 @@ static inline void mei_hcsr_set(struct mei_device *dev, u32 reg)
|
||||||
mei_hcsr_write(dev, reg);
|
mei_hcsr_write(dev, reg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mei_hcsr_set_hig - set host interrupt (set H_IG)
|
||||||
|
*
|
||||||
|
* @dev: the device structure
|
||||||
|
*/
|
||||||
|
static inline void mei_hcsr_set_hig(struct mei_device *dev)
|
||||||
|
{
|
||||||
|
u32 hcsr;
|
||||||
|
|
||||||
|
hcsr = mei_hcsr_read(dev) | H_IG;
|
||||||
|
mei_hcsr_set(dev, hcsr);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* mei_me_d0i3c_read - Reads 32bit data from the D0I3C register
|
* mei_me_d0i3c_read - Reads 32bit data from the D0I3C register
|
||||||
*
|
*
|
||||||
|
@ -380,6 +393,19 @@ static bool mei_me_hw_is_ready(struct mei_device *dev)
|
||||||
return (mecsr & ME_RDY_HRA) == ME_RDY_HRA;
|
return (mecsr & ME_RDY_HRA) == ME_RDY_HRA;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mei_me_hw_is_resetting - check whether the me(hw) is in reset
|
||||||
|
*
|
||||||
|
* @dev: mei device
|
||||||
|
* Return: bool
|
||||||
|
*/
|
||||||
|
static bool mei_me_hw_is_resetting(struct mei_device *dev)
|
||||||
|
{
|
||||||
|
u32 mecsr = mei_me_mecsr_read(dev);
|
||||||
|
|
||||||
|
return (mecsr & ME_RST_HRA) == ME_RST_HRA;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* mei_me_hw_ready_wait - wait until the me(hw) has turned ready
|
* mei_me_hw_ready_wait - wait until the me(hw) has turned ready
|
||||||
* or timeout is reached
|
* or timeout is reached
|
||||||
|
@ -505,7 +531,6 @@ static int mei_me_hbuf_write(struct mei_device *dev,
|
||||||
unsigned long rem;
|
unsigned long rem;
|
||||||
unsigned long length = header->length;
|
unsigned long length = header->length;
|
||||||
u32 *reg_buf = (u32 *)buf;
|
u32 *reg_buf = (u32 *)buf;
|
||||||
u32 hcsr;
|
|
||||||
u32 dw_cnt;
|
u32 dw_cnt;
|
||||||
int i;
|
int i;
|
||||||
int empty_slots;
|
int empty_slots;
|
||||||
|
@ -532,8 +557,7 @@ static int mei_me_hbuf_write(struct mei_device *dev,
|
||||||
mei_me_hcbww_write(dev, reg);
|
mei_me_hcbww_write(dev, reg);
|
||||||
}
|
}
|
||||||
|
|
||||||
hcsr = mei_hcsr_read(dev) | H_IG;
|
mei_hcsr_set_hig(dev);
|
||||||
mei_hcsr_set(dev, hcsr);
|
|
||||||
if (!mei_me_hw_is_ready(dev))
|
if (!mei_me_hw_is_ready(dev))
|
||||||
return -EIO;
|
return -EIO;
|
||||||
|
|
||||||
|
@ -580,7 +604,6 @@ static int mei_me_read_slots(struct mei_device *dev, unsigned char *buffer,
|
||||||
unsigned long buffer_length)
|
unsigned long buffer_length)
|
||||||
{
|
{
|
||||||
u32 *reg_buf = (u32 *)buffer;
|
u32 *reg_buf = (u32 *)buffer;
|
||||||
u32 hcsr;
|
|
||||||
|
|
||||||
for (; buffer_length >= sizeof(u32); buffer_length -= sizeof(u32))
|
for (; buffer_length >= sizeof(u32); buffer_length -= sizeof(u32))
|
||||||
*reg_buf++ = mei_me_mecbrw_read(dev);
|
*reg_buf++ = mei_me_mecbrw_read(dev);
|
||||||
|
@ -591,8 +614,7 @@ static int mei_me_read_slots(struct mei_device *dev, unsigned char *buffer,
|
||||||
memcpy(reg_buf, ®, buffer_length);
|
memcpy(reg_buf, ®, buffer_length);
|
||||||
}
|
}
|
||||||
|
|
||||||
hcsr = mei_hcsr_read(dev) | H_IG;
|
mei_hcsr_set_hig(dev);
|
||||||
mei_hcsr_set(dev, hcsr);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1189,7 +1211,7 @@ irqreturn_t mei_me_irq_quick_handler(int irq, void *dev_id)
|
||||||
irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id)
|
irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id)
|
||||||
{
|
{
|
||||||
struct mei_device *dev = (struct mei_device *) dev_id;
|
struct mei_device *dev = (struct mei_device *) dev_id;
|
||||||
struct mei_cl_cb complete_list;
|
struct list_head cmpl_list;
|
||||||
s32 slots;
|
s32 slots;
|
||||||
u32 hcsr;
|
u32 hcsr;
|
||||||
int rets = 0;
|
int rets = 0;
|
||||||
|
@ -1201,7 +1223,7 @@ irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id)
|
||||||
hcsr = mei_hcsr_read(dev);
|
hcsr = mei_hcsr_read(dev);
|
||||||
me_intr_clear(dev, hcsr);
|
me_intr_clear(dev, hcsr);
|
||||||
|
|
||||||
mei_io_list_init(&complete_list);
|
INIT_LIST_HEAD(&cmpl_list);
|
||||||
|
|
||||||
/* check if ME wants a reset */
|
/* check if ME wants a reset */
|
||||||
if (!mei_hw_is_ready(dev) && dev->dev_state != MEI_DEV_RESETTING) {
|
if (!mei_hw_is_ready(dev) && dev->dev_state != MEI_DEV_RESETTING) {
|
||||||
|
@ -1210,6 +1232,9 @@ irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id)
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mei_me_hw_is_resetting(dev))
|
||||||
|
mei_hcsr_set_hig(dev);
|
||||||
|
|
||||||
mei_me_pg_intr(dev, me_intr_src(hcsr));
|
mei_me_pg_intr(dev, me_intr_src(hcsr));
|
||||||
|
|
||||||
/* check if we need to start the dev */
|
/* check if we need to start the dev */
|
||||||
|
@ -1227,7 +1252,7 @@ irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id)
|
||||||
slots = mei_count_full_read_slots(dev);
|
slots = mei_count_full_read_slots(dev);
|
||||||
while (slots > 0) {
|
while (slots > 0) {
|
||||||
dev_dbg(dev->dev, "slots to read = %08x\n", slots);
|
dev_dbg(dev->dev, "slots to read = %08x\n", slots);
|
||||||
rets = mei_irq_read_handler(dev, &complete_list, &slots);
|
rets = mei_irq_read_handler(dev, &cmpl_list, &slots);
|
||||||
/* There is a race between ME write and interrupt delivery:
|
/* There is a race between ME write and interrupt delivery:
|
||||||
* Not all data is always available immediately after the
|
* Not all data is always available immediately after the
|
||||||
* interrupt, so try to read again on the next interrupt.
|
* interrupt, so try to read again on the next interrupt.
|
||||||
|
@ -1252,11 +1277,11 @@ irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id)
|
||||||
*/
|
*/
|
||||||
if (dev->pg_event != MEI_PG_EVENT_WAIT &&
|
if (dev->pg_event != MEI_PG_EVENT_WAIT &&
|
||||||
dev->pg_event != MEI_PG_EVENT_RECEIVED) {
|
dev->pg_event != MEI_PG_EVENT_RECEIVED) {
|
||||||
rets = mei_irq_write_handler(dev, &complete_list);
|
rets = mei_irq_write_handler(dev, &cmpl_list);
|
||||||
dev->hbuf_is_ready = mei_hbuf_is_ready(dev);
|
dev->hbuf_is_ready = mei_hbuf_is_ready(dev);
|
||||||
}
|
}
|
||||||
|
|
||||||
mei_irq_compl_handler(dev, &complete_list);
|
mei_irq_compl_handler(dev, &cmpl_list);
|
||||||
|
|
||||||
end:
|
end:
|
||||||
dev_dbg(dev->dev, "interrupt thread end ret = %d\n", rets);
|
dev_dbg(dev->dev, "interrupt thread end ret = %d\n", rets);
|
||||||
|
@ -1389,7 +1414,7 @@ const struct mei_cfg mei_me_pch8_sps_cfg = {
|
||||||
* @pdev: The pci device structure
|
* @pdev: The pci device structure
|
||||||
* @cfg: per device generation config
|
* @cfg: per device generation config
|
||||||
*
|
*
|
||||||
* Return: The mei_device_device pointer on success, NULL on failure.
|
* Return: The mei_device pointer on success, NULL on failure.
|
||||||
*/
|
*/
|
||||||
struct mei_device *mei_me_dev_init(struct pci_dev *pdev,
|
struct mei_device *mei_me_dev_init(struct pci_dev *pdev,
|
||||||
const struct mei_cfg *cfg)
|
const struct mei_cfg *cfg)
|
||||||
|
@ -1397,7 +1422,7 @@ struct mei_device *mei_me_dev_init(struct pci_dev *pdev,
|
||||||
struct mei_device *dev;
|
struct mei_device *dev;
|
||||||
struct mei_me_hw *hw;
|
struct mei_me_hw *hw;
|
||||||
|
|
||||||
dev = kzalloc(sizeof(struct mei_device) +
|
dev = devm_kzalloc(&pdev->dev, sizeof(struct mei_device) +
|
||||||
sizeof(struct mei_me_hw), GFP_KERNEL);
|
sizeof(struct mei_me_hw), GFP_KERNEL);
|
||||||
if (!dev)
|
if (!dev)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
|
@ -1057,7 +1057,7 @@ irqreturn_t mei_txe_irq_thread_handler(int irq, void *dev_id)
|
||||||
{
|
{
|
||||||
struct mei_device *dev = (struct mei_device *) dev_id;
|
struct mei_device *dev = (struct mei_device *) dev_id;
|
||||||
struct mei_txe_hw *hw = to_txe_hw(dev);
|
struct mei_txe_hw *hw = to_txe_hw(dev);
|
||||||
struct mei_cl_cb complete_list;
|
struct list_head cmpl_list;
|
||||||
s32 slots;
|
s32 slots;
|
||||||
int rets = 0;
|
int rets = 0;
|
||||||
|
|
||||||
|
@ -1069,7 +1069,7 @@ irqreturn_t mei_txe_irq_thread_handler(int irq, void *dev_id)
|
||||||
|
|
||||||
/* initialize our complete list */
|
/* initialize our complete list */
|
||||||
mutex_lock(&dev->device_lock);
|
mutex_lock(&dev->device_lock);
|
||||||
mei_io_list_init(&complete_list);
|
INIT_LIST_HEAD(&cmpl_list);
|
||||||
|
|
||||||
if (pci_dev_msi_enabled(to_pci_dev(dev->dev)))
|
if (pci_dev_msi_enabled(to_pci_dev(dev->dev)))
|
||||||
mei_txe_check_and_ack_intrs(dev, true);
|
mei_txe_check_and_ack_intrs(dev, true);
|
||||||
|
@ -1126,7 +1126,7 @@ irqreturn_t mei_txe_irq_thread_handler(int irq, void *dev_id)
|
||||||
slots = mei_count_full_read_slots(dev);
|
slots = mei_count_full_read_slots(dev);
|
||||||
if (test_and_clear_bit(TXE_INTR_OUT_DB_BIT, &hw->intr_cause)) {
|
if (test_and_clear_bit(TXE_INTR_OUT_DB_BIT, &hw->intr_cause)) {
|
||||||
/* Read from TXE */
|
/* Read from TXE */
|
||||||
rets = mei_irq_read_handler(dev, &complete_list, &slots);
|
rets = mei_irq_read_handler(dev, &cmpl_list, &slots);
|
||||||
if (rets && dev->dev_state != MEI_DEV_RESETTING) {
|
if (rets && dev->dev_state != MEI_DEV_RESETTING) {
|
||||||
dev_err(dev->dev,
|
dev_err(dev->dev,
|
||||||
"mei_irq_read_handler ret = %d.\n", rets);
|
"mei_irq_read_handler ret = %d.\n", rets);
|
||||||
|
@ -1144,14 +1144,14 @@ irqreturn_t mei_txe_irq_thread_handler(int irq, void *dev_id)
|
||||||
if (hw->aliveness && dev->hbuf_is_ready) {
|
if (hw->aliveness && dev->hbuf_is_ready) {
|
||||||
/* get the real register value */
|
/* get the real register value */
|
||||||
dev->hbuf_is_ready = mei_hbuf_is_ready(dev);
|
dev->hbuf_is_ready = mei_hbuf_is_ready(dev);
|
||||||
rets = mei_irq_write_handler(dev, &complete_list);
|
rets = mei_irq_write_handler(dev, &cmpl_list);
|
||||||
if (rets && rets != -EMSGSIZE)
|
if (rets && rets != -EMSGSIZE)
|
||||||
dev_err(dev->dev, "mei_irq_write_handler ret = %d.\n",
|
dev_err(dev->dev, "mei_irq_write_handler ret = %d.\n",
|
||||||
rets);
|
rets);
|
||||||
dev->hbuf_is_ready = mei_hbuf_is_ready(dev);
|
dev->hbuf_is_ready = mei_hbuf_is_ready(dev);
|
||||||
}
|
}
|
||||||
|
|
||||||
mei_irq_compl_handler(dev, &complete_list);
|
mei_irq_compl_handler(dev, &cmpl_list);
|
||||||
|
|
||||||
end:
|
end:
|
||||||
dev_dbg(dev->dev, "interrupt thread end ret = %d\n", rets);
|
dev_dbg(dev->dev, "interrupt thread end ret = %d\n", rets);
|
||||||
|
@ -1207,7 +1207,7 @@ struct mei_device *mei_txe_dev_init(struct pci_dev *pdev)
|
||||||
struct mei_device *dev;
|
struct mei_device *dev;
|
||||||
struct mei_txe_hw *hw;
|
struct mei_txe_hw *hw;
|
||||||
|
|
||||||
dev = kzalloc(sizeof(struct mei_device) +
|
dev = devm_kzalloc(&pdev->dev, sizeof(struct mei_device) +
|
||||||
sizeof(struct mei_txe_hw), GFP_KERNEL);
|
sizeof(struct mei_txe_hw), GFP_KERNEL);
|
||||||
if (!dev)
|
if (!dev)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
|
@ -45,7 +45,7 @@
|
||||||
* @intr_cause: translated interrupt cause
|
* @intr_cause: translated interrupt cause
|
||||||
*/
|
*/
|
||||||
struct mei_txe_hw {
|
struct mei_txe_hw {
|
||||||
void __iomem *mem_addr[NUM_OF_MEM_BARS];
|
void __iomem * const *mem_addr;
|
||||||
u32 aliveness;
|
u32 aliveness;
|
||||||
u32 readiness;
|
u32 readiness;
|
||||||
u32 slots;
|
u32 slots;
|
||||||
|
|
|
@ -349,16 +349,16 @@ EXPORT_SYMBOL_GPL(mei_stop);
|
||||||
bool mei_write_is_idle(struct mei_device *dev)
|
bool mei_write_is_idle(struct mei_device *dev)
|
||||||
{
|
{
|
||||||
bool idle = (dev->dev_state == MEI_DEV_ENABLED &&
|
bool idle = (dev->dev_state == MEI_DEV_ENABLED &&
|
||||||
list_empty(&dev->ctrl_wr_list.list) &&
|
list_empty(&dev->ctrl_wr_list) &&
|
||||||
list_empty(&dev->write_list.list) &&
|
list_empty(&dev->write_list) &&
|
||||||
list_empty(&dev->write_waiting_list.list));
|
list_empty(&dev->write_waiting_list));
|
||||||
|
|
||||||
dev_dbg(dev->dev, "write pg: is idle[%d] state=%s ctrl=%01d write=%01d wwait=%01d\n",
|
dev_dbg(dev->dev, "write pg: is idle[%d] state=%s ctrl=%01d write=%01d wwait=%01d\n",
|
||||||
idle,
|
idle,
|
||||||
mei_dev_state_str(dev->dev_state),
|
mei_dev_state_str(dev->dev_state),
|
||||||
list_empty(&dev->ctrl_wr_list.list),
|
list_empty(&dev->ctrl_wr_list),
|
||||||
list_empty(&dev->write_list.list),
|
list_empty(&dev->write_list),
|
||||||
list_empty(&dev->write_waiting_list.list));
|
list_empty(&dev->write_waiting_list));
|
||||||
|
|
||||||
return idle;
|
return idle;
|
||||||
}
|
}
|
||||||
|
@ -388,17 +388,17 @@ void mei_device_init(struct mei_device *dev,
|
||||||
dev->dev_state = MEI_DEV_INITIALIZING;
|
dev->dev_state = MEI_DEV_INITIALIZING;
|
||||||
dev->reset_count = 0;
|
dev->reset_count = 0;
|
||||||
|
|
||||||
mei_io_list_init(&dev->write_list);
|
INIT_LIST_HEAD(&dev->write_list);
|
||||||
mei_io_list_init(&dev->write_waiting_list);
|
INIT_LIST_HEAD(&dev->write_waiting_list);
|
||||||
mei_io_list_init(&dev->ctrl_wr_list);
|
INIT_LIST_HEAD(&dev->ctrl_wr_list);
|
||||||
mei_io_list_init(&dev->ctrl_rd_list);
|
INIT_LIST_HEAD(&dev->ctrl_rd_list);
|
||||||
|
|
||||||
INIT_DELAYED_WORK(&dev->timer_work, mei_timer);
|
INIT_DELAYED_WORK(&dev->timer_work, mei_timer);
|
||||||
INIT_WORK(&dev->reset_work, mei_reset_work);
|
INIT_WORK(&dev->reset_work, mei_reset_work);
|
||||||
INIT_WORK(&dev->bus_rescan_work, mei_cl_bus_rescan_work);
|
INIT_WORK(&dev->bus_rescan_work, mei_cl_bus_rescan_work);
|
||||||
|
|
||||||
INIT_LIST_HEAD(&dev->iamthif_cl.link);
|
INIT_LIST_HEAD(&dev->iamthif_cl.link);
|
||||||
mei_io_list_init(&dev->amthif_cmd_list);
|
INIT_LIST_HEAD(&dev->amthif_cmd_list);
|
||||||
|
|
||||||
bitmap_zero(dev->host_clients_map, MEI_CLIENTS_MAX);
|
bitmap_zero(dev->host_clients_map, MEI_CLIENTS_MAX);
|
||||||
dev->open_handle_count = 0;
|
dev->open_handle_count = 0;
|
||||||
|
|
|
@ -35,14 +35,14 @@
|
||||||
* for the completed callbacks
|
* for the completed callbacks
|
||||||
*
|
*
|
||||||
* @dev: mei device
|
* @dev: mei device
|
||||||
* @compl_list: list of completed cbs
|
* @cmpl_list: list of completed cbs
|
||||||
*/
|
*/
|
||||||
void mei_irq_compl_handler(struct mei_device *dev, struct mei_cl_cb *compl_list)
|
void mei_irq_compl_handler(struct mei_device *dev, struct list_head *cmpl_list)
|
||||||
{
|
{
|
||||||
struct mei_cl_cb *cb, *next;
|
struct mei_cl_cb *cb, *next;
|
||||||
struct mei_cl *cl;
|
struct mei_cl *cl;
|
||||||
|
|
||||||
list_for_each_entry_safe(cb, next, &compl_list->list, list) {
|
list_for_each_entry_safe(cb, next, cmpl_list, list) {
|
||||||
cl = cb->cl;
|
cl = cb->cl;
|
||||||
list_del_init(&cb->list);
|
list_del_init(&cb->list);
|
||||||
|
|
||||||
|
@ -92,13 +92,13 @@ void mei_irq_discard_msg(struct mei_device *dev, struct mei_msg_hdr *hdr)
|
||||||
*
|
*
|
||||||
* @cl: reading client
|
* @cl: reading client
|
||||||
* @mei_hdr: header of mei client message
|
* @mei_hdr: header of mei client message
|
||||||
* @complete_list: completion list
|
* @cmpl_list: completion list
|
||||||
*
|
*
|
||||||
* Return: always 0
|
* Return: always 0
|
||||||
*/
|
*/
|
||||||
int mei_cl_irq_read_msg(struct mei_cl *cl,
|
int mei_cl_irq_read_msg(struct mei_cl *cl,
|
||||||
struct mei_msg_hdr *mei_hdr,
|
struct mei_msg_hdr *mei_hdr,
|
||||||
struct mei_cl_cb *complete_list)
|
struct list_head *cmpl_list)
|
||||||
{
|
{
|
||||||
struct mei_device *dev = cl->dev;
|
struct mei_device *dev = cl->dev;
|
||||||
struct mei_cl_cb *cb;
|
struct mei_cl_cb *cb;
|
||||||
|
@ -144,7 +144,7 @@ int mei_cl_irq_read_msg(struct mei_cl *cl,
|
||||||
|
|
||||||
if (mei_hdr->msg_complete) {
|
if (mei_hdr->msg_complete) {
|
||||||
cl_dbg(dev, cl, "completed read length = %zu\n", cb->buf_idx);
|
cl_dbg(dev, cl, "completed read length = %zu\n", cb->buf_idx);
|
||||||
list_move_tail(&cb->list, &complete_list->list);
|
list_move_tail(&cb->list, cmpl_list);
|
||||||
} else {
|
} else {
|
||||||
pm_runtime_mark_last_busy(dev->dev);
|
pm_runtime_mark_last_busy(dev->dev);
|
||||||
pm_request_autosuspend(dev->dev);
|
pm_request_autosuspend(dev->dev);
|
||||||
|
@ -154,7 +154,7 @@ int mei_cl_irq_read_msg(struct mei_cl *cl,
|
||||||
|
|
||||||
discard:
|
discard:
|
||||||
if (cb)
|
if (cb)
|
||||||
list_move_tail(&cb->list, &complete_list->list);
|
list_move_tail(&cb->list, cmpl_list);
|
||||||
mei_irq_discard_msg(dev, mei_hdr);
|
mei_irq_discard_msg(dev, mei_hdr);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -169,7 +169,7 @@ int mei_cl_irq_read_msg(struct mei_cl *cl,
|
||||||
* Return: 0, OK; otherwise, error.
|
* Return: 0, OK; otherwise, error.
|
||||||
*/
|
*/
|
||||||
static int mei_cl_irq_disconnect_rsp(struct mei_cl *cl, struct mei_cl_cb *cb,
|
static int mei_cl_irq_disconnect_rsp(struct mei_cl *cl, struct mei_cl_cb *cb,
|
||||||
struct mei_cl_cb *cmpl_list)
|
struct list_head *cmpl_list)
|
||||||
{
|
{
|
||||||
struct mei_device *dev = cl->dev;
|
struct mei_device *dev = cl->dev;
|
||||||
u32 msg_slots;
|
u32 msg_slots;
|
||||||
|
@ -183,7 +183,7 @@ static int mei_cl_irq_disconnect_rsp(struct mei_cl *cl, struct mei_cl_cb *cb,
|
||||||
return -EMSGSIZE;
|
return -EMSGSIZE;
|
||||||
|
|
||||||
ret = mei_hbm_cl_disconnect_rsp(dev, cl);
|
ret = mei_hbm_cl_disconnect_rsp(dev, cl);
|
||||||
list_move_tail(&cb->list, &cmpl_list->list);
|
list_move_tail(&cb->list, cmpl_list);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -199,7 +199,7 @@ static int mei_cl_irq_disconnect_rsp(struct mei_cl *cl, struct mei_cl_cb *cb,
|
||||||
* Return: 0, OK; otherwise, error.
|
* Return: 0, OK; otherwise, error.
|
||||||
*/
|
*/
|
||||||
static int mei_cl_irq_read(struct mei_cl *cl, struct mei_cl_cb *cb,
|
static int mei_cl_irq_read(struct mei_cl *cl, struct mei_cl_cb *cb,
|
||||||
struct mei_cl_cb *cmpl_list)
|
struct list_head *cmpl_list)
|
||||||
{
|
{
|
||||||
struct mei_device *dev = cl->dev;
|
struct mei_device *dev = cl->dev;
|
||||||
u32 msg_slots;
|
u32 msg_slots;
|
||||||
|
@ -219,7 +219,7 @@ static int mei_cl_irq_read(struct mei_cl *cl, struct mei_cl_cb *cb,
|
||||||
if (ret) {
|
if (ret) {
|
||||||
cl->status = ret;
|
cl->status = ret;
|
||||||
cb->buf_idx = 0;
|
cb->buf_idx = 0;
|
||||||
list_move_tail(&cb->list, &cmpl_list->list);
|
list_move_tail(&cb->list, cmpl_list);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -249,7 +249,7 @@ static inline bool hdr_is_fixed(struct mei_msg_hdr *mei_hdr)
|
||||||
* Return: 0 on success, <0 on failure.
|
* Return: 0 on success, <0 on failure.
|
||||||
*/
|
*/
|
||||||
int mei_irq_read_handler(struct mei_device *dev,
|
int mei_irq_read_handler(struct mei_device *dev,
|
||||||
struct mei_cl_cb *cmpl_list, s32 *slots)
|
struct list_head *cmpl_list, s32 *slots)
|
||||||
{
|
{
|
||||||
struct mei_msg_hdr *mei_hdr;
|
struct mei_msg_hdr *mei_hdr;
|
||||||
struct mei_cl *cl;
|
struct mei_cl *cl;
|
||||||
|
@ -347,12 +347,11 @@ EXPORT_SYMBOL_GPL(mei_irq_read_handler);
|
||||||
*
|
*
|
||||||
* Return: 0 on success, <0 on failure.
|
* Return: 0 on success, <0 on failure.
|
||||||
*/
|
*/
|
||||||
int mei_irq_write_handler(struct mei_device *dev, struct mei_cl_cb *cmpl_list)
|
int mei_irq_write_handler(struct mei_device *dev, struct list_head *cmpl_list)
|
||||||
{
|
{
|
||||||
|
|
||||||
struct mei_cl *cl;
|
struct mei_cl *cl;
|
||||||
struct mei_cl_cb *cb, *next;
|
struct mei_cl_cb *cb, *next;
|
||||||
struct mei_cl_cb *list;
|
|
||||||
s32 slots;
|
s32 slots;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
|
@ -367,19 +366,18 @@ int mei_irq_write_handler(struct mei_device *dev, struct mei_cl_cb *cmpl_list)
|
||||||
/* complete all waiting for write CB */
|
/* complete all waiting for write CB */
|
||||||
dev_dbg(dev->dev, "complete all waiting for write cb.\n");
|
dev_dbg(dev->dev, "complete all waiting for write cb.\n");
|
||||||
|
|
||||||
list = &dev->write_waiting_list;
|
list_for_each_entry_safe(cb, next, &dev->write_waiting_list, list) {
|
||||||
list_for_each_entry_safe(cb, next, &list->list, list) {
|
|
||||||
cl = cb->cl;
|
cl = cb->cl;
|
||||||
|
|
||||||
cl->status = 0;
|
cl->status = 0;
|
||||||
cl_dbg(dev, cl, "MEI WRITE COMPLETE\n");
|
cl_dbg(dev, cl, "MEI WRITE COMPLETE\n");
|
||||||
cl->writing_state = MEI_WRITE_COMPLETE;
|
cl->writing_state = MEI_WRITE_COMPLETE;
|
||||||
list_move_tail(&cb->list, &cmpl_list->list);
|
list_move_tail(&cb->list, cmpl_list);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* complete control write list CB */
|
/* complete control write list CB */
|
||||||
dev_dbg(dev->dev, "complete control write list cb.\n");
|
dev_dbg(dev->dev, "complete control write list cb.\n");
|
||||||
list_for_each_entry_safe(cb, next, &dev->ctrl_wr_list.list, list) {
|
list_for_each_entry_safe(cb, next, &dev->ctrl_wr_list, list) {
|
||||||
cl = cb->cl;
|
cl = cb->cl;
|
||||||
switch (cb->fop_type) {
|
switch (cb->fop_type) {
|
||||||
case MEI_FOP_DISCONNECT:
|
case MEI_FOP_DISCONNECT:
|
||||||
|
@ -423,7 +421,7 @@ int mei_irq_write_handler(struct mei_device *dev, struct mei_cl_cb *cmpl_list)
|
||||||
}
|
}
|
||||||
/* complete write list CB */
|
/* complete write list CB */
|
||||||
dev_dbg(dev->dev, "complete write list cb.\n");
|
dev_dbg(dev->dev, "complete write list cb.\n");
|
||||||
list_for_each_entry_safe(cb, next, &dev->write_list.list, list) {
|
list_for_each_entry_safe(cb, next, &dev->write_list, list) {
|
||||||
cl = cb->cl;
|
cl = cb->cl;
|
||||||
if (cl == &dev->iamthif_cl)
|
if (cl == &dev->iamthif_cl)
|
||||||
ret = mei_amthif_irq_write(cl, cb, cmpl_list);
|
ret = mei_amthif_irq_write(cl, cb, cmpl_list);
|
||||||
|
|
|
@ -182,32 +182,36 @@ static ssize_t mei_read(struct file *file, char __user *ubuf,
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rets == -EBUSY &&
|
|
||||||
!mei_cl_enqueue_ctrl_wr_cb(cl, length, MEI_FOP_READ, file)) {
|
|
||||||
rets = -ENOMEM;
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
again:
|
||||||
mutex_unlock(&dev->device_lock);
|
mutex_unlock(&dev->device_lock);
|
||||||
|
|
||||||
if (wait_event_interruptible(cl->rx_wait,
|
if (wait_event_interruptible(cl->rx_wait,
|
||||||
(!list_empty(&cl->rd_completed)) ||
|
!list_empty(&cl->rd_completed) ||
|
||||||
(!mei_cl_is_connected(cl)))) {
|
!mei_cl_is_connected(cl))) {
|
||||||
|
|
||||||
if (signal_pending(current))
|
if (signal_pending(current))
|
||||||
return -EINTR;
|
return -EINTR;
|
||||||
return -ERESTARTSYS;
|
return -ERESTARTSYS;
|
||||||
}
|
}
|
||||||
|
|
||||||
mutex_lock(&dev->device_lock);
|
mutex_lock(&dev->device_lock);
|
||||||
|
|
||||||
if (!mei_cl_is_connected(cl)) {
|
if (!mei_cl_is_connected(cl)) {
|
||||||
rets = -ENODEV;
|
rets = -ENODEV;
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
cb = mei_cl_read_cb(cl, file);
|
cb = mei_cl_read_cb(cl, file);
|
||||||
} while (!cb);
|
if (!cb) {
|
||||||
|
/*
|
||||||
|
* For amthif all the waiters are woken up,
|
||||||
|
* but only fp with matching cb->fp get the cb,
|
||||||
|
* the others have to return to wait on read.
|
||||||
|
*/
|
||||||
|
if (cl == &dev->iamthif_cl)
|
||||||
|
goto again;
|
||||||
|
|
||||||
|
rets = 0;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
copy_buffer:
|
copy_buffer:
|
||||||
/* now copy the data to user space */
|
/* now copy the data to user space */
|
||||||
|
|
|
@ -328,6 +328,8 @@ ssize_t __mei_cl_recv(struct mei_cl *cl, u8 *buf, size_t length,
|
||||||
bool mei_cl_bus_rx_event(struct mei_cl *cl);
|
bool mei_cl_bus_rx_event(struct mei_cl *cl);
|
||||||
bool mei_cl_bus_notify_event(struct mei_cl *cl);
|
bool mei_cl_bus_notify_event(struct mei_cl *cl);
|
||||||
void mei_cl_bus_remove_devices(struct mei_device *bus);
|
void mei_cl_bus_remove_devices(struct mei_device *bus);
|
||||||
|
bool mei_cl_bus_module_get(struct mei_cl *cl);
|
||||||
|
void mei_cl_bus_module_put(struct mei_cl *cl);
|
||||||
int mei_cl_bus_init(void);
|
int mei_cl_bus_init(void);
|
||||||
void mei_cl_bus_exit(void);
|
void mei_cl_bus_exit(void);
|
||||||
|
|
||||||
|
@ -439,10 +441,10 @@ struct mei_device {
|
||||||
struct cdev cdev;
|
struct cdev cdev;
|
||||||
int minor;
|
int minor;
|
||||||
|
|
||||||
struct mei_cl_cb write_list;
|
struct list_head write_list;
|
||||||
struct mei_cl_cb write_waiting_list;
|
struct list_head write_waiting_list;
|
||||||
struct mei_cl_cb ctrl_wr_list;
|
struct list_head ctrl_wr_list;
|
||||||
struct mei_cl_cb ctrl_rd_list;
|
struct list_head ctrl_rd_list;
|
||||||
|
|
||||||
struct list_head file_list;
|
struct list_head file_list;
|
||||||
long open_handle_count;
|
long open_handle_count;
|
||||||
|
@ -499,7 +501,7 @@ struct mei_device {
|
||||||
bool override_fixed_address;
|
bool override_fixed_address;
|
||||||
|
|
||||||
/* amthif list for cmd waiting */
|
/* amthif list for cmd waiting */
|
||||||
struct mei_cl_cb amthif_cmd_list;
|
struct list_head amthif_cmd_list;
|
||||||
struct mei_cl iamthif_cl;
|
struct mei_cl iamthif_cl;
|
||||||
long iamthif_open_count;
|
long iamthif_open_count;
|
||||||
u32 iamthif_stall_timer;
|
u32 iamthif_stall_timer;
|
||||||
|
@ -571,10 +573,10 @@ void mei_cancel_work(struct mei_device *dev);
|
||||||
void mei_timer(struct work_struct *work);
|
void mei_timer(struct work_struct *work);
|
||||||
void mei_schedule_stall_timer(struct mei_device *dev);
|
void mei_schedule_stall_timer(struct mei_device *dev);
|
||||||
int mei_irq_read_handler(struct mei_device *dev,
|
int mei_irq_read_handler(struct mei_device *dev,
|
||||||
struct mei_cl_cb *cmpl_list, s32 *slots);
|
struct list_head *cmpl_list, s32 *slots);
|
||||||
|
|
||||||
int mei_irq_write_handler(struct mei_device *dev, struct mei_cl_cb *cmpl_list);
|
int mei_irq_write_handler(struct mei_device *dev, struct list_head *cmpl_list);
|
||||||
void mei_irq_compl_handler(struct mei_device *dev, struct mei_cl_cb *cmpl_list);
|
void mei_irq_compl_handler(struct mei_device *dev, struct list_head *cmpl_list);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* AMTHIF - AMT Host Interface Functions
|
* AMTHIF - AMT Host Interface Functions
|
||||||
|
@ -590,12 +592,12 @@ int mei_amthif_release(struct mei_device *dev, struct file *file);
|
||||||
int mei_amthif_write(struct mei_cl *cl, struct mei_cl_cb *cb);
|
int mei_amthif_write(struct mei_cl *cl, struct mei_cl_cb *cb);
|
||||||
int mei_amthif_run_next_cmd(struct mei_device *dev);
|
int mei_amthif_run_next_cmd(struct mei_device *dev);
|
||||||
int mei_amthif_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb,
|
int mei_amthif_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb,
|
||||||
struct mei_cl_cb *cmpl_list);
|
struct list_head *cmpl_list);
|
||||||
|
|
||||||
void mei_amthif_complete(struct mei_cl *cl, struct mei_cl_cb *cb);
|
void mei_amthif_complete(struct mei_cl *cl, struct mei_cl_cb *cb);
|
||||||
int mei_amthif_irq_read_msg(struct mei_cl *cl,
|
int mei_amthif_irq_read_msg(struct mei_cl *cl,
|
||||||
struct mei_msg_hdr *mei_hdr,
|
struct mei_msg_hdr *mei_hdr,
|
||||||
struct mei_cl_cb *complete_list);
|
struct list_head *cmpl_list);
|
||||||
int mei_amthif_irq_read(struct mei_device *dev, s32 *slots);
|
int mei_amthif_irq_read(struct mei_device *dev, s32 *slots);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -149,18 +149,18 @@ static int mei_me_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
|
||||||
return -ENODEV;
|
return -ENODEV;
|
||||||
|
|
||||||
/* enable pci dev */
|
/* enable pci dev */
|
||||||
err = pci_enable_device(pdev);
|
err = pcim_enable_device(pdev);
|
||||||
if (err) {
|
if (err) {
|
||||||
dev_err(&pdev->dev, "failed to enable pci device.\n");
|
dev_err(&pdev->dev, "failed to enable pci device.\n");
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
/* set PCI host mastering */
|
/* set PCI host mastering */
|
||||||
pci_set_master(pdev);
|
pci_set_master(pdev);
|
||||||
/* pci request regions for mei driver */
|
/* pci request regions and mapping IO device memory for mei driver */
|
||||||
err = pci_request_regions(pdev, KBUILD_MODNAME);
|
err = pcim_iomap_regions(pdev, BIT(0), KBUILD_MODNAME);
|
||||||
if (err) {
|
if (err) {
|
||||||
dev_err(&pdev->dev, "failed to get pci regions.\n");
|
dev_err(&pdev->dev, "failed to get pci regions.\n");
|
||||||
goto disable_device;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dma_set_mask(&pdev->dev, DMA_BIT_MASK(64)) ||
|
if (dma_set_mask(&pdev->dev, DMA_BIT_MASK(64)) ||
|
||||||
|
@ -173,24 +173,18 @@ static int mei_me_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
|
||||||
}
|
}
|
||||||
if (err) {
|
if (err) {
|
||||||
dev_err(&pdev->dev, "No usable DMA configuration, aborting\n");
|
dev_err(&pdev->dev, "No usable DMA configuration, aborting\n");
|
||||||
goto release_regions;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* allocates and initializes the mei dev structure */
|
/* allocates and initializes the mei dev structure */
|
||||||
dev = mei_me_dev_init(pdev, cfg);
|
dev = mei_me_dev_init(pdev, cfg);
|
||||||
if (!dev) {
|
if (!dev) {
|
||||||
err = -ENOMEM;
|
err = -ENOMEM;
|
||||||
goto release_regions;
|
goto end;
|
||||||
}
|
}
|
||||||
hw = to_me_hw(dev);
|
hw = to_me_hw(dev);
|
||||||
/* mapping IO device memory */
|
hw->mem_addr = pcim_iomap_table(pdev)[0];
|
||||||
hw->mem_addr = pci_iomap(pdev, 0, 0);
|
|
||||||
if (!hw->mem_addr) {
|
|
||||||
dev_err(&pdev->dev, "mapping I/O device memory failure.\n");
|
|
||||||
err = -ENOMEM;
|
|
||||||
goto free_device;
|
|
||||||
}
|
|
||||||
pci_enable_msi(pdev);
|
pci_enable_msi(pdev);
|
||||||
|
|
||||||
/* request and enable interrupt */
|
/* request and enable interrupt */
|
||||||
|
@ -203,7 +197,7 @@ static int mei_me_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
|
||||||
if (err) {
|
if (err) {
|
||||||
dev_err(&pdev->dev, "request_threaded_irq failure. irq = %d\n",
|
dev_err(&pdev->dev, "request_threaded_irq failure. irq = %d\n",
|
||||||
pdev->irq);
|
pdev->irq);
|
||||||
goto disable_msi;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mei_start(dev)) {
|
if (mei_start(dev)) {
|
||||||
|
@ -242,15 +236,6 @@ static int mei_me_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
|
||||||
mei_cancel_work(dev);
|
mei_cancel_work(dev);
|
||||||
mei_disable_interrupts(dev);
|
mei_disable_interrupts(dev);
|
||||||
free_irq(pdev->irq, dev);
|
free_irq(pdev->irq, dev);
|
||||||
disable_msi:
|
|
||||||
pci_disable_msi(pdev);
|
|
||||||
pci_iounmap(pdev, hw->mem_addr);
|
|
||||||
free_device:
|
|
||||||
kfree(dev);
|
|
||||||
release_regions:
|
|
||||||
pci_release_regions(pdev);
|
|
||||||
disable_device:
|
|
||||||
pci_disable_device(pdev);
|
|
||||||
end:
|
end:
|
||||||
dev_err(&pdev->dev, "initialization failed.\n");
|
dev_err(&pdev->dev, "initialization failed.\n");
|
||||||
return err;
|
return err;
|
||||||
|
@ -267,7 +252,6 @@ static int mei_me_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
|
||||||
static void mei_me_remove(struct pci_dev *pdev)
|
static void mei_me_remove(struct pci_dev *pdev)
|
||||||
{
|
{
|
||||||
struct mei_device *dev;
|
struct mei_device *dev;
|
||||||
struct mei_me_hw *hw;
|
|
||||||
|
|
||||||
dev = pci_get_drvdata(pdev);
|
dev = pci_get_drvdata(pdev);
|
||||||
if (!dev)
|
if (!dev)
|
||||||
|
@ -276,33 +260,19 @@ static void mei_me_remove(struct pci_dev *pdev)
|
||||||
if (mei_pg_is_enabled(dev))
|
if (mei_pg_is_enabled(dev))
|
||||||
pm_runtime_get_noresume(&pdev->dev);
|
pm_runtime_get_noresume(&pdev->dev);
|
||||||
|
|
||||||
hw = to_me_hw(dev);
|
|
||||||
|
|
||||||
|
|
||||||
dev_dbg(&pdev->dev, "stop\n");
|
dev_dbg(&pdev->dev, "stop\n");
|
||||||
mei_stop(dev);
|
mei_stop(dev);
|
||||||
|
|
||||||
if (!pci_dev_run_wake(pdev))
|
if (!pci_dev_run_wake(pdev))
|
||||||
mei_me_unset_pm_domain(dev);
|
mei_me_unset_pm_domain(dev);
|
||||||
|
|
||||||
/* disable interrupts */
|
|
||||||
mei_disable_interrupts(dev);
|
mei_disable_interrupts(dev);
|
||||||
|
|
||||||
free_irq(pdev->irq, dev);
|
free_irq(pdev->irq, dev);
|
||||||
pci_disable_msi(pdev);
|
|
||||||
|
|
||||||
if (hw->mem_addr)
|
|
||||||
pci_iounmap(pdev, hw->mem_addr);
|
|
||||||
|
|
||||||
mei_deregister(dev);
|
mei_deregister(dev);
|
||||||
|
|
||||||
kfree(dev);
|
|
||||||
|
|
||||||
pci_release_regions(pdev);
|
|
||||||
pci_disable_device(pdev);
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef CONFIG_PM_SLEEP
|
#ifdef CONFIG_PM_SLEEP
|
||||||
static int mei_me_pci_suspend(struct device *device)
|
static int mei_me_pci_suspend(struct device *device)
|
||||||
{
|
{
|
||||||
|
|
|
@ -52,17 +52,6 @@ static inline void mei_txe_set_pm_domain(struct mei_device *dev) {}
|
||||||
static inline void mei_txe_unset_pm_domain(struct mei_device *dev) {}
|
static inline void mei_txe_unset_pm_domain(struct mei_device *dev) {}
|
||||||
#endif /* CONFIG_PM */
|
#endif /* CONFIG_PM */
|
||||||
|
|
||||||
static void mei_txe_pci_iounmap(struct pci_dev *pdev, struct mei_txe_hw *hw)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
|
|
||||||
for (i = SEC_BAR; i < NUM_OF_MEM_BARS; i++) {
|
|
||||||
if (hw->mem_addr[i]) {
|
|
||||||
pci_iounmap(pdev, hw->mem_addr[i]);
|
|
||||||
hw->mem_addr[i] = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
/**
|
||||||
* mei_txe_probe - Device Initialization Routine
|
* mei_txe_probe - Device Initialization Routine
|
||||||
*
|
*
|
||||||
|
@ -75,22 +64,22 @@ static int mei_txe_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
|
||||||
{
|
{
|
||||||
struct mei_device *dev;
|
struct mei_device *dev;
|
||||||
struct mei_txe_hw *hw;
|
struct mei_txe_hw *hw;
|
||||||
|
const int mask = BIT(SEC_BAR) | BIT(BRIDGE_BAR);
|
||||||
int err;
|
int err;
|
||||||
int i;
|
|
||||||
|
|
||||||
/* enable pci dev */
|
/* enable pci dev */
|
||||||
err = pci_enable_device(pdev);
|
err = pcim_enable_device(pdev);
|
||||||
if (err) {
|
if (err) {
|
||||||
dev_err(&pdev->dev, "failed to enable pci device.\n");
|
dev_err(&pdev->dev, "failed to enable pci device.\n");
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
/* set PCI host mastering */
|
/* set PCI host mastering */
|
||||||
pci_set_master(pdev);
|
pci_set_master(pdev);
|
||||||
/* pci request regions for mei driver */
|
/* pci request regions and mapping IO device memory for mei driver */
|
||||||
err = pci_request_regions(pdev, KBUILD_MODNAME);
|
err = pcim_iomap_regions(pdev, mask, KBUILD_MODNAME);
|
||||||
if (err) {
|
if (err) {
|
||||||
dev_err(&pdev->dev, "failed to get pci regions.\n");
|
dev_err(&pdev->dev, "failed to get pci regions.\n");
|
||||||
goto disable_device;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
err = pci_set_dma_mask(pdev, DMA_BIT_MASK(36));
|
err = pci_set_dma_mask(pdev, DMA_BIT_MASK(36));
|
||||||
|
@ -98,7 +87,7 @@ static int mei_txe_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
|
||||||
err = pci_set_dma_mask(pdev, DMA_BIT_MASK(32));
|
err = pci_set_dma_mask(pdev, DMA_BIT_MASK(32));
|
||||||
if (err) {
|
if (err) {
|
||||||
dev_err(&pdev->dev, "No suitable DMA available.\n");
|
dev_err(&pdev->dev, "No suitable DMA available.\n");
|
||||||
goto release_regions;
|
goto end;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,20 +95,10 @@ static int mei_txe_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
|
||||||
dev = mei_txe_dev_init(pdev);
|
dev = mei_txe_dev_init(pdev);
|
||||||
if (!dev) {
|
if (!dev) {
|
||||||
err = -ENOMEM;
|
err = -ENOMEM;
|
||||||
goto release_regions;
|
goto end;
|
||||||
}
|
}
|
||||||
hw = to_txe_hw(dev);
|
hw = to_txe_hw(dev);
|
||||||
|
hw->mem_addr = pcim_iomap_table(pdev);
|
||||||
/* mapping IO device memory */
|
|
||||||
for (i = SEC_BAR; i < NUM_OF_MEM_BARS; i++) {
|
|
||||||
hw->mem_addr[i] = pci_iomap(pdev, i, 0);
|
|
||||||
if (!hw->mem_addr[i]) {
|
|
||||||
dev_err(&pdev->dev, "mapping I/O device memory failure.\n");
|
|
||||||
err = -ENOMEM;
|
|
||||||
goto free_device;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
pci_enable_msi(pdev);
|
pci_enable_msi(pdev);
|
||||||
|
|
||||||
|
@ -140,7 +119,7 @@ static int mei_txe_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
|
||||||
if (err) {
|
if (err) {
|
||||||
dev_err(&pdev->dev, "mei: request_threaded_irq failure. irq = %d\n",
|
dev_err(&pdev->dev, "mei: request_threaded_irq failure. irq = %d\n",
|
||||||
pdev->irq);
|
pdev->irq);
|
||||||
goto free_device;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mei_start(dev)) {
|
if (mei_start(dev)) {
|
||||||
|
@ -173,23 +152,9 @@ static int mei_txe_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
|
||||||
stop:
|
stop:
|
||||||
mei_stop(dev);
|
mei_stop(dev);
|
||||||
release_irq:
|
release_irq:
|
||||||
|
|
||||||
mei_cancel_work(dev);
|
mei_cancel_work(dev);
|
||||||
|
|
||||||
/* disable interrupts */
|
|
||||||
mei_disable_interrupts(dev);
|
mei_disable_interrupts(dev);
|
||||||
|
|
||||||
free_irq(pdev->irq, dev);
|
free_irq(pdev->irq, dev);
|
||||||
pci_disable_msi(pdev);
|
|
||||||
|
|
||||||
free_device:
|
|
||||||
mei_txe_pci_iounmap(pdev, hw);
|
|
||||||
|
|
||||||
kfree(dev);
|
|
||||||
release_regions:
|
|
||||||
pci_release_regions(pdev);
|
|
||||||
disable_device:
|
|
||||||
pci_disable_device(pdev);
|
|
||||||
end:
|
end:
|
||||||
dev_err(&pdev->dev, "initialization failed.\n");
|
dev_err(&pdev->dev, "initialization failed.\n");
|
||||||
return err;
|
return err;
|
||||||
|
@ -206,38 +171,24 @@ static int mei_txe_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
|
||||||
static void mei_txe_remove(struct pci_dev *pdev)
|
static void mei_txe_remove(struct pci_dev *pdev)
|
||||||
{
|
{
|
||||||
struct mei_device *dev;
|
struct mei_device *dev;
|
||||||
struct mei_txe_hw *hw;
|
|
||||||
|
|
||||||
dev = pci_get_drvdata(pdev);
|
dev = pci_get_drvdata(pdev);
|
||||||
if (!dev) {
|
if (!dev) {
|
||||||
dev_err(&pdev->dev, "mei: dev =NULL\n");
|
dev_err(&pdev->dev, "mei: dev == NULL\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
pm_runtime_get_noresume(&pdev->dev);
|
pm_runtime_get_noresume(&pdev->dev);
|
||||||
|
|
||||||
hw = to_txe_hw(dev);
|
|
||||||
|
|
||||||
mei_stop(dev);
|
mei_stop(dev);
|
||||||
|
|
||||||
if (!pci_dev_run_wake(pdev))
|
if (!pci_dev_run_wake(pdev))
|
||||||
mei_txe_unset_pm_domain(dev);
|
mei_txe_unset_pm_domain(dev);
|
||||||
|
|
||||||
/* disable interrupts */
|
|
||||||
mei_disable_interrupts(dev);
|
mei_disable_interrupts(dev);
|
||||||
free_irq(pdev->irq, dev);
|
free_irq(pdev->irq, dev);
|
||||||
pci_disable_msi(pdev);
|
|
||||||
|
|
||||||
pci_set_drvdata(pdev, NULL);
|
|
||||||
|
|
||||||
mei_txe_pci_iounmap(pdev, hw);
|
|
||||||
|
|
||||||
mei_deregister(dev);
|
mei_deregister(dev);
|
||||||
|
|
||||||
kfree(dev);
|
|
||||||
|
|
||||||
pci_release_regions(pdev);
|
|
||||||
pci_disable_device(pdev);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -292,7 +292,6 @@ static int vop_virtio_add_device(struct vop_vdev *vdev,
|
||||||
if (ret) {
|
if (ret) {
|
||||||
dev_err(vop_dev(vdev), "%s %d err %d\n",
|
dev_err(vop_dev(vdev), "%s %d err %d\n",
|
||||||
__func__, __LINE__, ret);
|
__func__, __LINE__, ret);
|
||||||
kfree(vdev);
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -56,6 +56,7 @@
|
||||||
#include <linux/list.h>
|
#include <linux/list.h>
|
||||||
#include <linux/notifier.h>
|
#include <linux/notifier.h>
|
||||||
#include <linux/reboot.h>
|
#include <linux/reboot.h>
|
||||||
|
#include <linux/workqueue.h>
|
||||||
#include <generated/utsrelease.h>
|
#include <generated/utsrelease.h>
|
||||||
|
|
||||||
#include <linux/io.h>
|
#include <linux/io.h>
|
||||||
|
@ -64,8 +65,6 @@
|
||||||
#define LCD_MINOR 156
|
#define LCD_MINOR 156
|
||||||
#define KEYPAD_MINOR 185
|
#define KEYPAD_MINOR 185
|
||||||
|
|
||||||
#define PANEL_VERSION "0.9.5"
|
|
||||||
|
|
||||||
#define LCD_MAXBYTES 256 /* max burst write */
|
#define LCD_MAXBYTES 256 /* max burst write */
|
||||||
|
|
||||||
#define KEYPAD_BUFFER 64
|
#define KEYPAD_BUFFER 64
|
||||||
|
@ -77,8 +76,8 @@
|
||||||
/* a key repeats this times INPUT_POLL_TIME */
|
/* a key repeats this times INPUT_POLL_TIME */
|
||||||
#define KEYPAD_REP_DELAY (2)
|
#define KEYPAD_REP_DELAY (2)
|
||||||
|
|
||||||
/* keep the light on this times INPUT_POLL_TIME for each flash */
|
/* keep the light on this many seconds for each flash */
|
||||||
#define FLASH_LIGHT_TEMPO (200)
|
#define FLASH_LIGHT_TEMPO (4)
|
||||||
|
|
||||||
/* converts an r_str() input to an active high, bits string : 000BAOSE */
|
/* converts an r_str() input to an active high, bits string : 000BAOSE */
|
||||||
#define PNL_PINPUT(a) ((((unsigned char)(a)) ^ 0x7F) >> 3)
|
#define PNL_PINPUT(a) ((((unsigned char)(a)) ^ 0x7F) >> 3)
|
||||||
|
@ -121,8 +120,6 @@
|
||||||
#define PIN_SELECP 17
|
#define PIN_SELECP 17
|
||||||
#define PIN_NOT_SET 127
|
#define PIN_NOT_SET 127
|
||||||
|
|
||||||
#define LCD_FLAG_S 0x0001
|
|
||||||
#define LCD_FLAG_ID 0x0002
|
|
||||||
#define LCD_FLAG_B 0x0004 /* blink on */
|
#define LCD_FLAG_B 0x0004 /* blink on */
|
||||||
#define LCD_FLAG_C 0x0008 /* cursor on */
|
#define LCD_FLAG_C 0x0008 /* cursor on */
|
||||||
#define LCD_FLAG_D 0x0010 /* display on */
|
#define LCD_FLAG_D 0x0010 /* display on */
|
||||||
|
@ -256,7 +253,10 @@ static struct {
|
||||||
int hwidth;
|
int hwidth;
|
||||||
int charset;
|
int charset;
|
||||||
int proto;
|
int proto;
|
||||||
int light_tempo;
|
|
||||||
|
struct delayed_work bl_work;
|
||||||
|
struct mutex bl_tempo_lock; /* Protects access to bl_tempo */
|
||||||
|
bool bl_tempo;
|
||||||
|
|
||||||
/* TODO: use union here? */
|
/* TODO: use union here? */
|
||||||
struct {
|
struct {
|
||||||
|
@ -661,8 +661,6 @@ static void lcd_get_bits(unsigned int port, int *val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void init_scan_timer(void);
|
|
||||||
|
|
||||||
/* sets data port bits according to current signals values */
|
/* sets data port bits according to current signals values */
|
||||||
static int set_data_bits(void)
|
static int set_data_bits(void)
|
||||||
{
|
{
|
||||||
|
@ -794,11 +792,8 @@ static void lcd_send_serial(int byte)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* turn the backlight on or off */
|
/* turn the backlight on or off */
|
||||||
static void lcd_backlight(int on)
|
static void __lcd_backlight(int on)
|
||||||
{
|
{
|
||||||
if (lcd.pins.bl == PIN_NONE)
|
|
||||||
return;
|
|
||||||
|
|
||||||
/* The backlight is activated by setting the AUTOFEED line to +5V */
|
/* The backlight is activated by setting the AUTOFEED line to +5V */
|
||||||
spin_lock_irq(&pprt_lock);
|
spin_lock_irq(&pprt_lock);
|
||||||
if (on)
|
if (on)
|
||||||
|
@ -809,6 +804,44 @@ static void lcd_backlight(int on)
|
||||||
spin_unlock_irq(&pprt_lock);
|
spin_unlock_irq(&pprt_lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void lcd_backlight(int on)
|
||||||
|
{
|
||||||
|
if (lcd.pins.bl == PIN_NONE)
|
||||||
|
return;
|
||||||
|
|
||||||
|
mutex_lock(&lcd.bl_tempo_lock);
|
||||||
|
if (!lcd.bl_tempo)
|
||||||
|
__lcd_backlight(on);
|
||||||
|
mutex_unlock(&lcd.bl_tempo_lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void lcd_bl_off(struct work_struct *work)
|
||||||
|
{
|
||||||
|
mutex_lock(&lcd.bl_tempo_lock);
|
||||||
|
if (lcd.bl_tempo) {
|
||||||
|
lcd.bl_tempo = false;
|
||||||
|
if (!(lcd.flags & LCD_FLAG_L))
|
||||||
|
__lcd_backlight(0);
|
||||||
|
}
|
||||||
|
mutex_unlock(&lcd.bl_tempo_lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* turn the backlight on for a little while */
|
||||||
|
static void lcd_poke(void)
|
||||||
|
{
|
||||||
|
if (lcd.pins.bl == PIN_NONE)
|
||||||
|
return;
|
||||||
|
|
||||||
|
cancel_delayed_work_sync(&lcd.bl_work);
|
||||||
|
|
||||||
|
mutex_lock(&lcd.bl_tempo_lock);
|
||||||
|
if (!lcd.bl_tempo && !(lcd.flags & LCD_FLAG_L))
|
||||||
|
__lcd_backlight(1);
|
||||||
|
lcd.bl_tempo = true;
|
||||||
|
schedule_delayed_work(&lcd.bl_work, FLASH_LIGHT_TEMPO * HZ);
|
||||||
|
mutex_unlock(&lcd.bl_tempo_lock);
|
||||||
|
}
|
||||||
|
|
||||||
/* send a command to the LCD panel in serial mode */
|
/* send a command to the LCD panel in serial mode */
|
||||||
static void lcd_write_cmd_s(int cmd)
|
static void lcd_write_cmd_s(int cmd)
|
||||||
{
|
{
|
||||||
|
@ -907,6 +940,13 @@ static void lcd_gotoxy(void)
|
||||||
(lcd.hwidth - 1) : lcd.bwidth - 1));
|
(lcd.hwidth - 1) : lcd.bwidth - 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void lcd_home(void)
|
||||||
|
{
|
||||||
|
lcd.addr.x = 0;
|
||||||
|
lcd.addr.y = 0;
|
||||||
|
lcd_gotoxy();
|
||||||
|
}
|
||||||
|
|
||||||
static void lcd_print(char c)
|
static void lcd_print(char c)
|
||||||
{
|
{
|
||||||
if (lcd.addr.x < lcd.bwidth) {
|
if (lcd.addr.x < lcd.bwidth) {
|
||||||
|
@ -925,9 +965,7 @@ static void lcd_clear_fast_s(void)
|
||||||
{
|
{
|
||||||
int pos;
|
int pos;
|
||||||
|
|
||||||
lcd.addr.x = 0;
|
lcd_home();
|
||||||
lcd.addr.y = 0;
|
|
||||||
lcd_gotoxy();
|
|
||||||
|
|
||||||
spin_lock_irq(&pprt_lock);
|
spin_lock_irq(&pprt_lock);
|
||||||
for (pos = 0; pos < lcd.height * lcd.hwidth; pos++) {
|
for (pos = 0; pos < lcd.height * lcd.hwidth; pos++) {
|
||||||
|
@ -939,9 +977,7 @@ static void lcd_clear_fast_s(void)
|
||||||
}
|
}
|
||||||
spin_unlock_irq(&pprt_lock);
|
spin_unlock_irq(&pprt_lock);
|
||||||
|
|
||||||
lcd.addr.x = 0;
|
lcd_home();
|
||||||
lcd.addr.y = 0;
|
|
||||||
lcd_gotoxy();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* fills the display with spaces and resets X/Y */
|
/* fills the display with spaces and resets X/Y */
|
||||||
|
@ -949,9 +985,7 @@ static void lcd_clear_fast_p8(void)
|
||||||
{
|
{
|
||||||
int pos;
|
int pos;
|
||||||
|
|
||||||
lcd.addr.x = 0;
|
lcd_home();
|
||||||
lcd.addr.y = 0;
|
|
||||||
lcd_gotoxy();
|
|
||||||
|
|
||||||
spin_lock_irq(&pprt_lock);
|
spin_lock_irq(&pprt_lock);
|
||||||
for (pos = 0; pos < lcd.height * lcd.hwidth; pos++) {
|
for (pos = 0; pos < lcd.height * lcd.hwidth; pos++) {
|
||||||
|
@ -977,9 +1011,7 @@ static void lcd_clear_fast_p8(void)
|
||||||
}
|
}
|
||||||
spin_unlock_irq(&pprt_lock);
|
spin_unlock_irq(&pprt_lock);
|
||||||
|
|
||||||
lcd.addr.x = 0;
|
lcd_home();
|
||||||
lcd.addr.y = 0;
|
|
||||||
lcd_gotoxy();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* fills the display with spaces and resets X/Y */
|
/* fills the display with spaces and resets X/Y */
|
||||||
|
@ -987,9 +1019,7 @@ static void lcd_clear_fast_tilcd(void)
|
||||||
{
|
{
|
||||||
int pos;
|
int pos;
|
||||||
|
|
||||||
lcd.addr.x = 0;
|
lcd_home();
|
||||||
lcd.addr.y = 0;
|
|
||||||
lcd_gotoxy();
|
|
||||||
|
|
||||||
spin_lock_irq(&pprt_lock);
|
spin_lock_irq(&pprt_lock);
|
||||||
for (pos = 0; pos < lcd.height * lcd.hwidth; pos++) {
|
for (pos = 0; pos < lcd.height * lcd.hwidth; pos++) {
|
||||||
|
@ -1000,9 +1030,7 @@ static void lcd_clear_fast_tilcd(void)
|
||||||
|
|
||||||
spin_unlock_irq(&pprt_lock);
|
spin_unlock_irq(&pprt_lock);
|
||||||
|
|
||||||
lcd.addr.x = 0;
|
lcd_home();
|
||||||
lcd.addr.y = 0;
|
|
||||||
lcd_gotoxy();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* clears the display and resets X/Y */
|
/* clears the display and resets X/Y */
|
||||||
|
@ -1108,13 +1136,8 @@ static inline int handle_lcd_special_code(void)
|
||||||
processed = 1;
|
processed = 1;
|
||||||
break;
|
break;
|
||||||
case '*':
|
case '*':
|
||||||
/* flash back light using the keypad timer */
|
/* flash back light */
|
||||||
if (scan_timer.function) {
|
lcd_poke();
|
||||||
if (lcd.light_tempo == 0 &&
|
|
||||||
((lcd.flags & LCD_FLAG_L) == 0))
|
|
||||||
lcd_backlight(1);
|
|
||||||
lcd.light_tempo = FLASH_LIGHT_TEMPO;
|
|
||||||
}
|
|
||||||
processed = 1;
|
processed = 1;
|
||||||
break;
|
break;
|
||||||
case 'f': /* Small Font */
|
case 'f': /* Small Font */
|
||||||
|
@ -1278,21 +1301,14 @@ static inline int handle_lcd_special_code(void)
|
||||||
lcd_write_cmd(LCD_CMD_FUNCTION_SET
|
lcd_write_cmd(LCD_CMD_FUNCTION_SET
|
||||||
| LCD_CMD_DATA_LEN_8BITS
|
| LCD_CMD_DATA_LEN_8BITS
|
||||||
| ((lcd.flags & LCD_FLAG_F)
|
| ((lcd.flags & LCD_FLAG_F)
|
||||||
? LCD_CMD_TWO_LINES : 0)
|
|
||||||
| ((lcd.flags & LCD_FLAG_N)
|
|
||||||
? LCD_CMD_FONT_5X10_DOTS
|
? LCD_CMD_FONT_5X10_DOTS
|
||||||
|
: 0)
|
||||||
|
| ((lcd.flags & LCD_FLAG_N)
|
||||||
|
? LCD_CMD_TWO_LINES
|
||||||
: 0));
|
: 0));
|
||||||
/* check whether L flag was changed */
|
/* check whether L flag was changed */
|
||||||
else if ((oldflags ^ lcd.flags) & (LCD_FLAG_L)) {
|
else if ((oldflags ^ lcd.flags) & (LCD_FLAG_L))
|
||||||
if (lcd.flags & (LCD_FLAG_L))
|
lcd_backlight(!!(lcd.flags & LCD_FLAG_L));
|
||||||
lcd_backlight(1);
|
|
||||||
else if (lcd.light_tempo == 0)
|
|
||||||
/*
|
|
||||||
* switch off the light only when the tempo
|
|
||||||
* lighting is gone
|
|
||||||
*/
|
|
||||||
lcd_backlight(0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return processed;
|
return processed;
|
||||||
|
@ -1376,9 +1392,7 @@ static void lcd_write_char(char c)
|
||||||
processed = 1;
|
processed = 1;
|
||||||
} else if (!strcmp(lcd.esc_seq.buf, "[H")) {
|
} else if (!strcmp(lcd.esc_seq.buf, "[H")) {
|
||||||
/* cursor to home */
|
/* cursor to home */
|
||||||
lcd.addr.x = 0;
|
lcd_home();
|
||||||
lcd.addr.y = 0;
|
|
||||||
lcd_gotoxy();
|
|
||||||
processed = 1;
|
processed = 1;
|
||||||
}
|
}
|
||||||
/* codes starting with ^[[L */
|
/* codes starting with ^[[L */
|
||||||
|
@ -1625,8 +1639,10 @@ static void lcd_init(void)
|
||||||
else
|
else
|
||||||
lcd_char_conv = NULL;
|
lcd_char_conv = NULL;
|
||||||
|
|
||||||
if (lcd.pins.bl != PIN_NONE)
|
if (lcd.pins.bl != PIN_NONE) {
|
||||||
init_scan_timer();
|
mutex_init(&lcd.bl_tempo_lock);
|
||||||
|
INIT_DELAYED_WORK(&lcd.bl_work, lcd_bl_off);
|
||||||
|
}
|
||||||
|
|
||||||
pin_to_bits(lcd.pins.e, lcd_bits[LCD_PORT_D][LCD_BIT_E],
|
pin_to_bits(lcd.pins.e, lcd_bits[LCD_PORT_D][LCD_BIT_E],
|
||||||
lcd_bits[LCD_PORT_C][LCD_BIT_E]);
|
lcd_bits[LCD_PORT_C][LCD_BIT_E]);
|
||||||
|
@ -1655,14 +1671,11 @@ static void lcd_init(void)
|
||||||
panel_lcd_print("\x1b[Lc\x1b[Lb\x1b[L*" CONFIG_PANEL_BOOT_MESSAGE);
|
panel_lcd_print("\x1b[Lc\x1b[Lb\x1b[L*" CONFIG_PANEL_BOOT_MESSAGE);
|
||||||
#endif
|
#endif
|
||||||
#else
|
#else
|
||||||
panel_lcd_print("\x1b[Lc\x1b[Lb\x1b[L*Linux-" UTS_RELEASE "\nPanel-"
|
panel_lcd_print("\x1b[Lc\x1b[Lb\x1b[L*Linux-" UTS_RELEASE);
|
||||||
PANEL_VERSION);
|
|
||||||
#endif
|
#endif
|
||||||
lcd.addr.x = 0;
|
|
||||||
lcd.addr.y = 0;
|
|
||||||
/* clear the display on the next device opening */
|
/* clear the display on the next device opening */
|
||||||
lcd.must_clear = true;
|
lcd.must_clear = true;
|
||||||
lcd_gotoxy();
|
lcd_home();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -1997,19 +2010,8 @@ static void panel_scan_timer(void)
|
||||||
panel_process_inputs();
|
panel_process_inputs();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lcd.enabled && lcd.initialized) {
|
if (keypressed && lcd.enabled && lcd.initialized)
|
||||||
if (keypressed) {
|
lcd_poke();
|
||||||
if (lcd.light_tempo == 0 &&
|
|
||||||
((lcd.flags & LCD_FLAG_L) == 0))
|
|
||||||
lcd_backlight(1);
|
|
||||||
lcd.light_tempo = FLASH_LIGHT_TEMPO;
|
|
||||||
} else if (lcd.light_tempo > 0) {
|
|
||||||
lcd.light_tempo--;
|
|
||||||
if (lcd.light_tempo == 0 &&
|
|
||||||
((lcd.flags & LCD_FLAG_L) == 0))
|
|
||||||
lcd_backlight(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mod_timer(&scan_timer, jiffies + INPUT_POLL_TIME);
|
mod_timer(&scan_timer, jiffies + INPUT_POLL_TIME);
|
||||||
}
|
}
|
||||||
|
@ -2270,16 +2272,18 @@ static void panel_detach(struct parport *port)
|
||||||
if (scan_timer.function)
|
if (scan_timer.function)
|
||||||
del_timer_sync(&scan_timer);
|
del_timer_sync(&scan_timer);
|
||||||
|
|
||||||
if (pprt) {
|
|
||||||
if (keypad.enabled) {
|
if (keypad.enabled) {
|
||||||
misc_deregister(&keypad_dev);
|
misc_deregister(&keypad_dev);
|
||||||
keypad_initialized = 0;
|
keypad_initialized = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lcd.enabled) {
|
if (lcd.enabled) {
|
||||||
panel_lcd_print("\x0cLCD driver " PANEL_VERSION
|
panel_lcd_print("\x0cLCD driver unloaded.\x1b[Lc\x1b[Lb\x1b[L-");
|
||||||
"\nunloaded.\x1b[Lc\x1b[Lb\x1b[L-");
|
|
||||||
misc_deregister(&lcd_dev);
|
misc_deregister(&lcd_dev);
|
||||||
|
if (lcd.pins.bl != PIN_NONE) {
|
||||||
|
cancel_delayed_work_sync(&lcd.bl_work);
|
||||||
|
__lcd_backlight(0);
|
||||||
|
}
|
||||||
lcd.initialized = false;
|
lcd.initialized = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2289,7 +2293,6 @@ static void panel_detach(struct parport *port)
|
||||||
pprt = NULL;
|
pprt = NULL;
|
||||||
unregister_reboot_notifier(&panel_notifier);
|
unregister_reboot_notifier(&panel_notifier);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
static struct parport_driver panel_driver = {
|
static struct parport_driver panel_driver = {
|
||||||
.name = "panel",
|
.name = "panel",
|
||||||
|
@ -2400,7 +2403,7 @@ static int __init panel_init_module(void)
|
||||||
|
|
||||||
if (!lcd.enabled && !keypad.enabled) {
|
if (!lcd.enabled && !keypad.enabled) {
|
||||||
/* no device enabled, let's exit */
|
/* no device enabled, let's exit */
|
||||||
pr_err("driver version " PANEL_VERSION " disabled.\n");
|
pr_err("panel driver disabled.\n");
|
||||||
return -ENODEV;
|
return -ENODEV;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2411,12 +2414,10 @@ static int __init panel_init_module(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pprt)
|
if (pprt)
|
||||||
pr_info("driver version " PANEL_VERSION
|
pr_info("panel driver registered on parport%d (io=0x%lx).\n",
|
||||||
" registered on parport%d (io=0x%lx).\n", parport,
|
parport, pprt->port->base);
|
||||||
pprt->port->base);
|
|
||||||
else
|
else
|
||||||
pr_info("driver version " PANEL_VERSION
|
pr_info("panel driver not yet registered\n");
|
||||||
" not yet registered\n");
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,105 @@
|
||||||
|
/*
|
||||||
|
* SRAM protect-exec region helper functions
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017 Texas Instruments Incorporated - http://www.ti.com/
|
||||||
|
* Dave Gerlach
|
||||||
|
*
|
||||||
|
* 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 "as is" WITHOUT ANY WARRANTY of any
|
||||||
|
* kind, whether express or implied; without even the implied warranty
|
||||||
|
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/device.h>
|
||||||
|
#include <linux/genalloc.h>
|
||||||
|
#include <linux/sram.h>
|
||||||
|
|
||||||
|
#include <asm/cacheflush.h>
|
||||||
|
|
||||||
|
#include "sram.h"
|
||||||
|
|
||||||
|
static DEFINE_MUTEX(exec_pool_list_mutex);
|
||||||
|
static LIST_HEAD(exec_pool_list);
|
||||||
|
|
||||||
|
int sram_check_protect_exec(struct sram_dev *sram, struct sram_reserve *block,
|
||||||
|
struct sram_partition *part)
|
||||||
|
{
|
||||||
|
unsigned long base = (unsigned long)part->base;
|
||||||
|
unsigned long end = base + block->size;
|
||||||
|
|
||||||
|
if (!PAGE_ALIGNED(base) || !PAGE_ALIGNED(end)) {
|
||||||
|
dev_err(sram->dev,
|
||||||
|
"SRAM pool marked with 'protect-exec' is not page aligned and will not be created.\n");
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int sram_add_protect_exec(struct sram_partition *part)
|
||||||
|
{
|
||||||
|
mutex_lock(&exec_pool_list_mutex);
|
||||||
|
list_add_tail(&part->list, &exec_pool_list);
|
||||||
|
mutex_unlock(&exec_pool_list_mutex);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sram_exec_copy - copy data to a protected executable region of sram
|
||||||
|
*
|
||||||
|
* @pool: struct gen_pool retrieved that is part of this sram
|
||||||
|
* @dst: Destination address for the copy, that must be inside pool
|
||||||
|
* @src: Source address for the data to copy
|
||||||
|
* @size: Size of copy to perform, which starting from dst, must reside in pool
|
||||||
|
*
|
||||||
|
* This helper function allows sram driver to act as central control location
|
||||||
|
* of 'protect-exec' pools which are normal sram pools but are always set
|
||||||
|
* read-only and executable except when copying data to them, at which point
|
||||||
|
* they are set to read-write non-executable, to make sure no memory is
|
||||||
|
* writeable and executable at the same time. This region must be page-aligned
|
||||||
|
* and is checked during probe, otherwise page attribute manipulation would
|
||||||
|
* not be possible.
|
||||||
|
*/
|
||||||
|
int sram_exec_copy(struct gen_pool *pool, void *dst, void *src,
|
||||||
|
size_t size)
|
||||||
|
{
|
||||||
|
struct sram_partition *part = NULL, *p;
|
||||||
|
unsigned long base;
|
||||||
|
int pages;
|
||||||
|
|
||||||
|
mutex_lock(&exec_pool_list_mutex);
|
||||||
|
list_for_each_entry(p, &exec_pool_list, list) {
|
||||||
|
if (p->pool == pool)
|
||||||
|
part = p;
|
||||||
|
}
|
||||||
|
mutex_unlock(&exec_pool_list_mutex);
|
||||||
|
|
||||||
|
if (!part)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
if (!addr_in_gen_pool(pool, (unsigned long)dst, size))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
base = (unsigned long)part->base;
|
||||||
|
pages = PAGE_ALIGN(size) / PAGE_SIZE;
|
||||||
|
|
||||||
|
mutex_lock(&part->lock);
|
||||||
|
|
||||||
|
set_memory_nx((unsigned long)base, pages);
|
||||||
|
set_memory_rw((unsigned long)base, pages);
|
||||||
|
|
||||||
|
memcpy(dst, src, size);
|
||||||
|
|
||||||
|
set_memory_ro((unsigned long)base, pages);
|
||||||
|
set_memory_x((unsigned long)base, pages);
|
||||||
|
|
||||||
|
mutex_unlock(&part->lock);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(sram_exec_copy);
|
|
@ -31,36 +31,10 @@
|
||||||
#include <linux/mfd/syscon.h>
|
#include <linux/mfd/syscon.h>
|
||||||
#include <soc/at91/atmel-secumod.h>
|
#include <soc/at91/atmel-secumod.h>
|
||||||
|
|
||||||
|
#include "sram.h"
|
||||||
|
|
||||||
#define SRAM_GRANULARITY 32
|
#define SRAM_GRANULARITY 32
|
||||||
|
|
||||||
struct sram_partition {
|
|
||||||
void __iomem *base;
|
|
||||||
|
|
||||||
struct gen_pool *pool;
|
|
||||||
struct bin_attribute battr;
|
|
||||||
struct mutex lock;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct sram_dev {
|
|
||||||
struct device *dev;
|
|
||||||
void __iomem *virt_base;
|
|
||||||
|
|
||||||
struct gen_pool *pool;
|
|
||||||
struct clk *clk;
|
|
||||||
|
|
||||||
struct sram_partition *partition;
|
|
||||||
u32 partitions;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct sram_reserve {
|
|
||||||
struct list_head list;
|
|
||||||
u32 start;
|
|
||||||
u32 size;
|
|
||||||
bool export;
|
|
||||||
bool pool;
|
|
||||||
const char *label;
|
|
||||||
};
|
|
||||||
|
|
||||||
static ssize_t sram_read(struct file *filp, struct kobject *kobj,
|
static ssize_t sram_read(struct file *filp, struct kobject *kobj,
|
||||||
struct bin_attribute *attr,
|
struct bin_attribute *attr,
|
||||||
char *buf, loff_t pos, size_t count)
|
char *buf, loff_t pos, size_t count)
|
||||||
|
@ -148,6 +122,18 @@ static int sram_add_partition(struct sram_dev *sram, struct sram_reserve *block,
|
||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
if (block->protect_exec) {
|
||||||
|
ret = sram_check_protect_exec(sram, block, part);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ret = sram_add_pool(sram, block, start, part);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
sram_add_protect_exec(part);
|
||||||
|
}
|
||||||
|
|
||||||
sram->partitions++;
|
sram->partitions++;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -233,7 +219,11 @@ static int sram_reserve_regions(struct sram_dev *sram, struct resource *res)
|
||||||
if (of_find_property(child, "pool", NULL))
|
if (of_find_property(child, "pool", NULL))
|
||||||
block->pool = true;
|
block->pool = true;
|
||||||
|
|
||||||
if ((block->export || block->pool) && block->size) {
|
if (of_find_property(child, "protect-exec", NULL))
|
||||||
|
block->protect_exec = true;
|
||||||
|
|
||||||
|
if ((block->export || block->pool || block->protect_exec) &&
|
||||||
|
block->size) {
|
||||||
exports++;
|
exports++;
|
||||||
|
|
||||||
label = NULL;
|
label = NULL;
|
||||||
|
@ -249,8 +239,10 @@ static int sram_reserve_regions(struct sram_dev *sram, struct resource *res)
|
||||||
|
|
||||||
block->label = devm_kstrdup(sram->dev,
|
block->label = devm_kstrdup(sram->dev,
|
||||||
label, GFP_KERNEL);
|
label, GFP_KERNEL);
|
||||||
if (!block->label)
|
if (!block->label) {
|
||||||
|
ret = -ENOMEM;
|
||||||
goto err_chunks;
|
goto err_chunks;
|
||||||
|
}
|
||||||
|
|
||||||
dev_dbg(sram->dev, "found %sblock '%s' 0x%x-0x%x\n",
|
dev_dbg(sram->dev, "found %sblock '%s' 0x%x-0x%x\n",
|
||||||
block->export ? "exported " : "", block->label,
|
block->export ? "exported " : "", block->label,
|
||||||
|
@ -293,7 +285,8 @@ static int sram_reserve_regions(struct sram_dev *sram, struct resource *res)
|
||||||
goto err_chunks;
|
goto err_chunks;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((block->export || block->pool) && block->size) {
|
if ((block->export || block->pool || block->protect_exec) &&
|
||||||
|
block->size) {
|
||||||
ret = sram_add_partition(sram, block,
|
ret = sram_add_partition(sram, block,
|
||||||
res->start + block->start);
|
res->start + block->start);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
/*
|
||||||
|
* Defines for the SRAM driver
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
#ifndef __SRAM_H
|
||||||
|
#define __SRAM_H
|
||||||
|
|
||||||
|
struct sram_partition {
|
||||||
|
void __iomem *base;
|
||||||
|
|
||||||
|
struct gen_pool *pool;
|
||||||
|
struct bin_attribute battr;
|
||||||
|
struct mutex lock;
|
||||||
|
struct list_head list;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct sram_dev {
|
||||||
|
struct device *dev;
|
||||||
|
void __iomem *virt_base;
|
||||||
|
|
||||||
|
struct gen_pool *pool;
|
||||||
|
struct clk *clk;
|
||||||
|
|
||||||
|
struct sram_partition *partition;
|
||||||
|
u32 partitions;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct sram_reserve {
|
||||||
|
struct list_head list;
|
||||||
|
u32 start;
|
||||||
|
u32 size;
|
||||||
|
bool export;
|
||||||
|
bool pool;
|
||||||
|
bool protect_exec;
|
||||||
|
const char *label;
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef CONFIG_SRAM_EXEC
|
||||||
|
int sram_check_protect_exec(struct sram_dev *sram, struct sram_reserve *block,
|
||||||
|
struct sram_partition *part);
|
||||||
|
int sram_add_protect_exec(struct sram_partition *part);
|
||||||
|
#else
|
||||||
|
static inline int sram_check_protect_exec(struct sram_dev *sram,
|
||||||
|
struct sram_reserve *block,
|
||||||
|
struct sram_partition *part)
|
||||||
|
{
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int sram_add_protect_exec(struct sram_partition *part)
|
||||||
|
{
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
#endif /* CONFIG_SRAM_EXEC */
|
||||||
|
#endif /* __SRAM_H */
|
|
@ -54,10 +54,7 @@ struct vmci_guest_device {
|
||||||
struct device *dev; /* PCI device we are attached to */
|
struct device *dev; /* PCI device we are attached to */
|
||||||
void __iomem *iobase;
|
void __iomem *iobase;
|
||||||
|
|
||||||
unsigned int irq;
|
|
||||||
unsigned int intr_type;
|
|
||||||
bool exclusive_vectors;
|
bool exclusive_vectors;
|
||||||
struct msix_entry msix_entries[VMCI_MAX_INTRS];
|
|
||||||
|
|
||||||
struct tasklet_struct datagram_tasklet;
|
struct tasklet_struct datagram_tasklet;
|
||||||
struct tasklet_struct bm_tasklet;
|
struct tasklet_struct bm_tasklet;
|
||||||
|
@ -368,30 +365,6 @@ static void vmci_process_bitmap(unsigned long data)
|
||||||
vmci_dbell_scan_notification_entries(dev->notification_bitmap);
|
vmci_dbell_scan_notification_entries(dev->notification_bitmap);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Enable MSI-X. Try exclusive vectors first, then shared vectors.
|
|
||||||
*/
|
|
||||||
static int vmci_enable_msix(struct pci_dev *pdev,
|
|
||||||
struct vmci_guest_device *vmci_dev)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
int result;
|
|
||||||
|
|
||||||
for (i = 0; i < VMCI_MAX_INTRS; ++i) {
|
|
||||||
vmci_dev->msix_entries[i].entry = i;
|
|
||||||
vmci_dev->msix_entries[i].vector = i;
|
|
||||||
}
|
|
||||||
|
|
||||||
result = pci_enable_msix_exact(pdev,
|
|
||||||
vmci_dev->msix_entries, VMCI_MAX_INTRS);
|
|
||||||
if (result == 0)
|
|
||||||
vmci_dev->exclusive_vectors = true;
|
|
||||||
else if (result == -ENOSPC)
|
|
||||||
result = pci_enable_msix_exact(pdev, vmci_dev->msix_entries, 1);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Interrupt handler for legacy or MSI interrupt, or for first MSI-X
|
* Interrupt handler for legacy or MSI interrupt, or for first MSI-X
|
||||||
* interrupt (vector VMCI_INTR_DATAGRAM).
|
* interrupt (vector VMCI_INTR_DATAGRAM).
|
||||||
|
@ -406,7 +379,7 @@ static irqreturn_t vmci_interrupt(int irq, void *_dev)
|
||||||
* Otherwise we must read the ICR to determine what to do.
|
* Otherwise we must read the ICR to determine what to do.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (dev->intr_type == VMCI_INTR_TYPE_MSIX && dev->exclusive_vectors) {
|
if (dev->exclusive_vectors) {
|
||||||
tasklet_schedule(&dev->datagram_tasklet);
|
tasklet_schedule(&dev->datagram_tasklet);
|
||||||
} else {
|
} else {
|
||||||
unsigned int icr;
|
unsigned int icr;
|
||||||
|
@ -491,7 +464,6 @@ static int vmci_guest_probe_device(struct pci_dev *pdev,
|
||||||
}
|
}
|
||||||
|
|
||||||
vmci_dev->dev = &pdev->dev;
|
vmci_dev->dev = &pdev->dev;
|
||||||
vmci_dev->intr_type = VMCI_INTR_TYPE_INTX;
|
|
||||||
vmci_dev->exclusive_vectors = false;
|
vmci_dev->exclusive_vectors = false;
|
||||||
vmci_dev->iobase = iobase;
|
vmci_dev->iobase = iobase;
|
||||||
|
|
||||||
|
@ -592,26 +564,26 @@ static int vmci_guest_probe_device(struct pci_dev *pdev,
|
||||||
* Enable interrupts. Try MSI-X first, then MSI, and then fallback on
|
* Enable interrupts. Try MSI-X first, then MSI, and then fallback on
|
||||||
* legacy interrupts.
|
* legacy interrupts.
|
||||||
*/
|
*/
|
||||||
if (!vmci_disable_msix && !vmci_enable_msix(pdev, vmci_dev)) {
|
error = pci_alloc_irq_vectors(pdev, VMCI_MAX_INTRS, VMCI_MAX_INTRS,
|
||||||
vmci_dev->intr_type = VMCI_INTR_TYPE_MSIX;
|
PCI_IRQ_MSIX);
|
||||||
vmci_dev->irq = vmci_dev->msix_entries[0].vector;
|
if (error) {
|
||||||
} else if (!vmci_disable_msi && !pci_enable_msi(pdev)) {
|
error = pci_alloc_irq_vectors(pdev, 1, 1,
|
||||||
vmci_dev->intr_type = VMCI_INTR_TYPE_MSI;
|
PCI_IRQ_MSIX | PCI_IRQ_MSI | PCI_IRQ_LEGACY);
|
||||||
vmci_dev->irq = pdev->irq;
|
if (error)
|
||||||
|
goto err_remove_bitmap;
|
||||||
} else {
|
} else {
|
||||||
vmci_dev->intr_type = VMCI_INTR_TYPE_INTX;
|
vmci_dev->exclusive_vectors = true;
|
||||||
vmci_dev->irq = pdev->irq;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Request IRQ for legacy or MSI interrupts, or for first
|
* Request IRQ for legacy or MSI interrupts, or for first
|
||||||
* MSI-X vector.
|
* MSI-X vector.
|
||||||
*/
|
*/
|
||||||
error = request_irq(vmci_dev->irq, vmci_interrupt, IRQF_SHARED,
|
error = request_irq(pci_irq_vector(pdev, 0), vmci_interrupt,
|
||||||
KBUILD_MODNAME, vmci_dev);
|
IRQF_SHARED, KBUILD_MODNAME, vmci_dev);
|
||||||
if (error) {
|
if (error) {
|
||||||
dev_err(&pdev->dev, "Irq %u in use: %d\n",
|
dev_err(&pdev->dev, "Irq %u in use: %d\n",
|
||||||
vmci_dev->irq, error);
|
pci_irq_vector(pdev, 0), error);
|
||||||
goto err_disable_msi;
|
goto err_disable_msi;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -622,13 +594,13 @@ static int vmci_guest_probe_device(struct pci_dev *pdev,
|
||||||
* between the vectors.
|
* between the vectors.
|
||||||
*/
|
*/
|
||||||
if (vmci_dev->exclusive_vectors) {
|
if (vmci_dev->exclusive_vectors) {
|
||||||
error = request_irq(vmci_dev->msix_entries[1].vector,
|
error = request_irq(pci_irq_vector(pdev, 1),
|
||||||
vmci_interrupt_bm, 0, KBUILD_MODNAME,
|
vmci_interrupt_bm, 0, KBUILD_MODNAME,
|
||||||
vmci_dev);
|
vmci_dev);
|
||||||
if (error) {
|
if (error) {
|
||||||
dev_err(&pdev->dev,
|
dev_err(&pdev->dev,
|
||||||
"Failed to allocate irq %u: %d\n",
|
"Failed to allocate irq %u: %d\n",
|
||||||
vmci_dev->msix_entries[1].vector, error);
|
pci_irq_vector(pdev, 1), error);
|
||||||
goto err_free_irq;
|
goto err_free_irq;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -651,15 +623,12 @@ static int vmci_guest_probe_device(struct pci_dev *pdev,
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
err_free_irq:
|
err_free_irq:
|
||||||
free_irq(vmci_dev->irq, vmci_dev);
|
free_irq(pci_irq_vector(pdev, 0), vmci_dev);
|
||||||
tasklet_kill(&vmci_dev->datagram_tasklet);
|
tasklet_kill(&vmci_dev->datagram_tasklet);
|
||||||
tasklet_kill(&vmci_dev->bm_tasklet);
|
tasklet_kill(&vmci_dev->bm_tasklet);
|
||||||
|
|
||||||
err_disable_msi:
|
err_disable_msi:
|
||||||
if (vmci_dev->intr_type == VMCI_INTR_TYPE_MSIX)
|
pci_free_irq_vectors(pdev);
|
||||||
pci_disable_msix(pdev);
|
|
||||||
else if (vmci_dev->intr_type == VMCI_INTR_TYPE_MSI)
|
|
||||||
pci_disable_msi(pdev);
|
|
||||||
|
|
||||||
vmci_err = vmci_event_unsubscribe(ctx_update_sub_id);
|
vmci_err = vmci_event_unsubscribe(ctx_update_sub_id);
|
||||||
if (vmci_err < VMCI_SUCCESS)
|
if (vmci_err < VMCI_SUCCESS)
|
||||||
|
@ -719,14 +688,10 @@ static void vmci_guest_remove_device(struct pci_dev *pdev)
|
||||||
* MSI-X, we might have multiple vectors, each with their own
|
* MSI-X, we might have multiple vectors, each with their own
|
||||||
* IRQ, which we must free too.
|
* IRQ, which we must free too.
|
||||||
*/
|
*/
|
||||||
free_irq(vmci_dev->irq, vmci_dev);
|
|
||||||
if (vmci_dev->intr_type == VMCI_INTR_TYPE_MSIX) {
|
|
||||||
if (vmci_dev->exclusive_vectors)
|
if (vmci_dev->exclusive_vectors)
|
||||||
free_irq(vmci_dev->msix_entries[1].vector, vmci_dev);
|
free_irq(pci_irq_vector(pdev, 1), vmci_dev);
|
||||||
pci_disable_msix(pdev);
|
free_irq(pci_irq_vector(pdev, 0), vmci_dev);
|
||||||
} else if (vmci_dev->intr_type == VMCI_INTR_TYPE_MSI) {
|
pci_free_irq_vectors(pdev);
|
||||||
pci_disable_msi(pdev);
|
|
||||||
}
|
|
||||||
|
|
||||||
tasklet_kill(&vmci_dev->datagram_tasklet);
|
tasklet_kill(&vmci_dev->datagram_tasklet);
|
||||||
tasklet_kill(&vmci_dev->bm_tasklet);
|
tasklet_kill(&vmci_dev->bm_tasklet);
|
||||||
|
|
|
@ -703,8 +703,6 @@ static u32 netvsc_copy_to_send_buf(struct netvsc_device *net_device,
|
||||||
char *dest = start + (section_index * net_device->send_section_size)
|
char *dest = start + (section_index * net_device->send_section_size)
|
||||||
+ pend_size;
|
+ pend_size;
|
||||||
int i;
|
int i;
|
||||||
bool is_data_pkt = (skb != NULL) ? true : false;
|
|
||||||
bool xmit_more = (skb != NULL) ? skb->xmit_more : false;
|
|
||||||
u32 msg_size = 0;
|
u32 msg_size = 0;
|
||||||
u32 padding = 0;
|
u32 padding = 0;
|
||||||
u32 remain = packet->total_data_buflen % net_device->pkt_align;
|
u32 remain = packet->total_data_buflen % net_device->pkt_align;
|
||||||
|
@ -712,7 +710,7 @@ static u32 netvsc_copy_to_send_buf(struct netvsc_device *net_device,
|
||||||
packet->page_buf_cnt;
|
packet->page_buf_cnt;
|
||||||
|
|
||||||
/* Add padding */
|
/* Add padding */
|
||||||
if (is_data_pkt && xmit_more && remain &&
|
if (skb && skb->xmit_more && remain &&
|
||||||
!packet->cp_partial) {
|
!packet->cp_partial) {
|
||||||
padding = net_device->pkt_align - remain;
|
padding = net_device->pkt_align - remain;
|
||||||
rndis_msg->msg_len += padding;
|
rndis_msg->msg_len += padding;
|
||||||
|
@ -754,7 +752,6 @@ static inline int netvsc_send_pkt(
|
||||||
int ret;
|
int ret;
|
||||||
struct hv_page_buffer *pgbuf;
|
struct hv_page_buffer *pgbuf;
|
||||||
u32 ring_avail = hv_ringbuf_avail_percent(&out_channel->outbound);
|
u32 ring_avail = hv_ringbuf_avail_percent(&out_channel->outbound);
|
||||||
bool xmit_more = (skb != NULL) ? skb->xmit_more : false;
|
|
||||||
|
|
||||||
nvmsg.hdr.msg_type = NVSP_MSG1_TYPE_SEND_RNDIS_PKT;
|
nvmsg.hdr.msg_type = NVSP_MSG1_TYPE_SEND_RNDIS_PKT;
|
||||||
if (skb != NULL) {
|
if (skb != NULL) {
|
||||||
|
@ -778,16 +775,6 @@ static inline int netvsc_send_pkt(
|
||||||
if (out_channel->rescind)
|
if (out_channel->rescind)
|
||||||
return -ENODEV;
|
return -ENODEV;
|
||||||
|
|
||||||
/*
|
|
||||||
* It is possible that once we successfully place this packet
|
|
||||||
* on the ringbuffer, we may stop the queue. In that case, we want
|
|
||||||
* to notify the host independent of the xmit_more flag. We don't
|
|
||||||
* need to be precise here; in the worst case we may signal the host
|
|
||||||
* unnecessarily.
|
|
||||||
*/
|
|
||||||
if (ring_avail < (RING_AVAIL_PERCENT_LOWATER + 1))
|
|
||||||
xmit_more = false;
|
|
||||||
|
|
||||||
if (packet->page_buf_cnt) {
|
if (packet->page_buf_cnt) {
|
||||||
pgbuf = packet->cp_partial ? (*pb) +
|
pgbuf = packet->cp_partial ? (*pb) +
|
||||||
packet->rmsg_pgcnt : (*pb);
|
packet->rmsg_pgcnt : (*pb);
|
||||||
|
@ -797,15 +784,13 @@ static inline int netvsc_send_pkt(
|
||||||
&nvmsg,
|
&nvmsg,
|
||||||
sizeof(struct nvsp_message),
|
sizeof(struct nvsp_message),
|
||||||
req_id,
|
req_id,
|
||||||
VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED,
|
VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED);
|
||||||
!xmit_more);
|
|
||||||
} else {
|
} else {
|
||||||
ret = vmbus_sendpacket_ctl(out_channel, &nvmsg,
|
ret = vmbus_sendpacket_ctl(out_channel, &nvmsg,
|
||||||
sizeof(struct nvsp_message),
|
sizeof(struct nvsp_message),
|
||||||
req_id,
|
req_id,
|
||||||
VM_PKT_DATA_INBAND,
|
VM_PKT_DATA_INBAND,
|
||||||
VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED,
|
VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED);
|
||||||
!xmit_more);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ret == 0) {
|
if (ret == 0) {
|
||||||
|
|
|
@ -608,7 +608,7 @@ static struct nvmem_device *nvmem_find(const char *name)
|
||||||
/**
|
/**
|
||||||
* of_nvmem_device_get() - Get nvmem device from a given id
|
* of_nvmem_device_get() - Get nvmem device from a given id
|
||||||
*
|
*
|
||||||
* @dev node: Device tree node that uses the nvmem device
|
* @np: Device tree node that uses the nvmem device.
|
||||||
* @id: nvmem name from nvmem-names property.
|
* @id: nvmem name from nvmem-names property.
|
||||||
*
|
*
|
||||||
* Return: ERR_PTR() on error or a valid pointer to a struct nvmem_device
|
* Return: ERR_PTR() on error or a valid pointer to a struct nvmem_device
|
||||||
|
@ -634,8 +634,8 @@ EXPORT_SYMBOL_GPL(of_nvmem_device_get);
|
||||||
/**
|
/**
|
||||||
* nvmem_device_get() - Get nvmem device from a given id
|
* nvmem_device_get() - Get nvmem device from a given id
|
||||||
*
|
*
|
||||||
* @dev : Device that uses the nvmem device
|
* @dev: Device that uses the nvmem device.
|
||||||
* @id: nvmem name from nvmem-names property.
|
* @dev_name: name of the requested nvmem device.
|
||||||
*
|
*
|
||||||
* Return: ERR_PTR() on error or a valid pointer to a struct nvmem_device
|
* Return: ERR_PTR() on error or a valid pointer to a struct nvmem_device
|
||||||
* on success.
|
* on success.
|
||||||
|
@ -674,6 +674,7 @@ static void devm_nvmem_device_release(struct device *dev, void *res)
|
||||||
/**
|
/**
|
||||||
* devm_nvmem_device_put() - put alredy got nvmem device
|
* devm_nvmem_device_put() - put alredy got nvmem device
|
||||||
*
|
*
|
||||||
|
* @dev: Device that uses the nvmem device.
|
||||||
* @nvmem: pointer to nvmem device allocated by devm_nvmem_cell_get(),
|
* @nvmem: pointer to nvmem device allocated by devm_nvmem_cell_get(),
|
||||||
* that needs to be released.
|
* that needs to be released.
|
||||||
*/
|
*/
|
||||||
|
@ -702,8 +703,8 @@ EXPORT_SYMBOL_GPL(nvmem_device_put);
|
||||||
/**
|
/**
|
||||||
* devm_nvmem_device_get() - Get nvmem cell of device form a given id
|
* devm_nvmem_device_get() - Get nvmem cell of device form a given id
|
||||||
*
|
*
|
||||||
* @dev node: Device tree node that uses the nvmem cell
|
* @dev: Device that requests the nvmem device.
|
||||||
* @id: nvmem name in nvmems property.
|
* @id: name id for the requested nvmem device.
|
||||||
*
|
*
|
||||||
* Return: ERR_PTR() on error or a valid pointer to a struct nvmem_cell
|
* Return: ERR_PTR() on error or a valid pointer to a struct nvmem_cell
|
||||||
* on success. The nvmem_cell will be freed by the automatically once the
|
* on success. The nvmem_cell will be freed by the automatically once the
|
||||||
|
@ -745,8 +746,10 @@ static struct nvmem_cell *nvmem_cell_get_from_list(const char *cell_id)
|
||||||
/**
|
/**
|
||||||
* of_nvmem_cell_get() - Get a nvmem cell from given device node and cell id
|
* of_nvmem_cell_get() - Get a nvmem cell from given device node and cell id
|
||||||
*
|
*
|
||||||
* @dev node: Device tree node that uses the nvmem cell
|
* @np: Device tree node that uses the nvmem cell.
|
||||||
* @id: nvmem cell name from nvmem-cell-names property.
|
* @name: nvmem cell name from nvmem-cell-names property, or NULL
|
||||||
|
* for the cell at index 0 (the lone cell with no accompanying
|
||||||
|
* nvmem-cell-names property).
|
||||||
*
|
*
|
||||||
* Return: Will be an ERR_PTR() on error or a valid pointer
|
* Return: Will be an ERR_PTR() on error or a valid pointer
|
||||||
* to a struct nvmem_cell. The nvmem_cell will be freed by the
|
* to a struct nvmem_cell. The nvmem_cell will be freed by the
|
||||||
|
@ -759,8 +762,11 @@ struct nvmem_cell *of_nvmem_cell_get(struct device_node *np,
|
||||||
struct nvmem_cell *cell;
|
struct nvmem_cell *cell;
|
||||||
struct nvmem_device *nvmem;
|
struct nvmem_device *nvmem;
|
||||||
const __be32 *addr;
|
const __be32 *addr;
|
||||||
int rval, len, index;
|
int rval, len;
|
||||||
|
int index = 0;
|
||||||
|
|
||||||
|
/* if cell name exists, find index to the name */
|
||||||
|
if (name)
|
||||||
index = of_property_match_string(np, "nvmem-cell-names", name);
|
index = of_property_match_string(np, "nvmem-cell-names", name);
|
||||||
|
|
||||||
cell_np = of_parse_phandle(np, "nvmem-cells", index);
|
cell_np = of_parse_phandle(np, "nvmem-cells", index);
|
||||||
|
@ -830,8 +836,8 @@ EXPORT_SYMBOL_GPL(of_nvmem_cell_get);
|
||||||
/**
|
/**
|
||||||
* nvmem_cell_get() - Get nvmem cell of device form a given cell name
|
* nvmem_cell_get() - Get nvmem cell of device form a given cell name
|
||||||
*
|
*
|
||||||
* @dev node: Device tree node that uses the nvmem cell
|
* @dev: Device that requests the nvmem cell.
|
||||||
* @id: nvmem cell name to get.
|
* @cell_id: nvmem cell name to get.
|
||||||
*
|
*
|
||||||
* Return: Will be an ERR_PTR() on error or a valid pointer
|
* Return: Will be an ERR_PTR() on error or a valid pointer
|
||||||
* to a struct nvmem_cell. The nvmem_cell will be freed by the
|
* to a struct nvmem_cell. The nvmem_cell will be freed by the
|
||||||
|
@ -859,8 +865,8 @@ static void devm_nvmem_cell_release(struct device *dev, void *res)
|
||||||
/**
|
/**
|
||||||
* devm_nvmem_cell_get() - Get nvmem cell of device form a given id
|
* devm_nvmem_cell_get() - Get nvmem cell of device form a given id
|
||||||
*
|
*
|
||||||
* @dev node: Device tree node that uses the nvmem cell
|
* @dev: Device that requests the nvmem cell.
|
||||||
* @id: nvmem id in nvmem-names property.
|
* @id: nvmem cell name id to get.
|
||||||
*
|
*
|
||||||
* Return: Will be an ERR_PTR() on error or a valid pointer
|
* Return: Will be an ERR_PTR() on error or a valid pointer
|
||||||
* to a struct nvmem_cell. The nvmem_cell will be freed by the
|
* to a struct nvmem_cell. The nvmem_cell will be freed by the
|
||||||
|
@ -900,7 +906,8 @@ static int devm_nvmem_cell_match(struct device *dev, void *res, void *data)
|
||||||
* devm_nvmem_cell_put() - Release previously allocated nvmem cell
|
* devm_nvmem_cell_put() - Release previously allocated nvmem cell
|
||||||
* from devm_nvmem_cell_get.
|
* from devm_nvmem_cell_get.
|
||||||
*
|
*
|
||||||
* @cell: Previously allocated nvmem cell by devm_nvmem_cell_get()
|
* @dev: Device that requests the nvmem cell.
|
||||||
|
* @cell: Previously allocated nvmem cell by devm_nvmem_cell_get().
|
||||||
*/
|
*/
|
||||||
void devm_nvmem_cell_put(struct device *dev, struct nvmem_cell *cell)
|
void devm_nvmem_cell_put(struct device *dev, struct nvmem_cell *cell)
|
||||||
{
|
{
|
||||||
|
@ -916,7 +923,7 @@ EXPORT_SYMBOL(devm_nvmem_cell_put);
|
||||||
/**
|
/**
|
||||||
* nvmem_cell_put() - Release previously allocated nvmem cell.
|
* nvmem_cell_put() - Release previously allocated nvmem cell.
|
||||||
*
|
*
|
||||||
* @cell: Previously allocated nvmem cell by nvmem_cell_get()
|
* @cell: Previously allocated nvmem cell by nvmem_cell_get().
|
||||||
*/
|
*/
|
||||||
void nvmem_cell_put(struct nvmem_cell *cell)
|
void nvmem_cell_put(struct nvmem_cell *cell)
|
||||||
{
|
{
|
||||||
|
@ -970,6 +977,7 @@ static int __nvmem_cell_read(struct nvmem_device *nvmem,
|
||||||
if (cell->bit_offset || cell->nbits)
|
if (cell->bit_offset || cell->nbits)
|
||||||
nvmem_shift_read_buffer_in_place(cell, buf);
|
nvmem_shift_read_buffer_in_place(cell, buf);
|
||||||
|
|
||||||
|
if (len)
|
||||||
*len = cell->bytes;
|
*len = cell->bytes;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -979,7 +987,8 @@ static int __nvmem_cell_read(struct nvmem_device *nvmem,
|
||||||
* nvmem_cell_read() - Read a given nvmem cell
|
* nvmem_cell_read() - Read a given nvmem cell
|
||||||
*
|
*
|
||||||
* @cell: nvmem cell to be read.
|
* @cell: nvmem cell to be read.
|
||||||
* @len: pointer to length of cell which will be populated on successful read.
|
* @len: pointer to length of cell which will be populated on successful read;
|
||||||
|
* can be NULL.
|
||||||
*
|
*
|
||||||
* Return: ERR_PTR() on error or a valid pointer to a buffer on success. The
|
* Return: ERR_PTR() on error or a valid pointer to a buffer on success. The
|
||||||
* buffer should be freed by the consumer with a kfree().
|
* buffer should be freed by the consumer with a kfree().
|
||||||
|
@ -1126,7 +1135,7 @@ EXPORT_SYMBOL_GPL(nvmem_device_cell_read);
|
||||||
* nvmem_device_cell_write() - Write cell to a given nvmem device
|
* nvmem_device_cell_write() - Write cell to a given nvmem device
|
||||||
*
|
*
|
||||||
* @nvmem: nvmem device to be written to.
|
* @nvmem: nvmem device to be written to.
|
||||||
* @info: nvmem cell info to be written
|
* @info: nvmem cell info to be written.
|
||||||
* @buf: buffer to be written to cell.
|
* @buf: buffer to be written to cell.
|
||||||
*
|
*
|
||||||
* Return: length of bytes written or negative error code on failure.
|
* Return: length of bytes written or negative error code on failure.
|
||||||
|
|
|
@ -73,6 +73,7 @@ static const struct of_device_id imx_ocotp_dt_ids[] = {
|
||||||
{ .compatible = "fsl,imx6q-ocotp", (void *)128 },
|
{ .compatible = "fsl,imx6q-ocotp", (void *)128 },
|
||||||
{ .compatible = "fsl,imx6sl-ocotp", (void *)64 },
|
{ .compatible = "fsl,imx6sl-ocotp", (void *)64 },
|
||||||
{ .compatible = "fsl,imx6sx-ocotp", (void *)128 },
|
{ .compatible = "fsl,imx6sx-ocotp", (void *)128 },
|
||||||
|
{ .compatible = "fsl,imx6ul-ocotp", (void *)128 },
|
||||||
{ },
|
{ },
|
||||||
};
|
};
|
||||||
MODULE_DEVICE_TABLE(of, imx_ocotp_dt_ids);
|
MODULE_DEVICE_TABLE(of, imx_ocotp_dt_ids);
|
||||||
|
|
|
@ -157,24 +157,27 @@ static int goldfish_new_pdev(void)
|
||||||
static irqreturn_t goldfish_pdev_bus_interrupt(int irq, void *dev_id)
|
static irqreturn_t goldfish_pdev_bus_interrupt(int irq, void *dev_id)
|
||||||
{
|
{
|
||||||
irqreturn_t ret = IRQ_NONE;
|
irqreturn_t ret = IRQ_NONE;
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
u32 op = readl(pdev_bus_base + PDEV_BUS_OP);
|
u32 op = readl(pdev_bus_base + PDEV_BUS_OP);
|
||||||
switch (op) {
|
|
||||||
case PDEV_BUS_OP_DONE:
|
|
||||||
return IRQ_NONE;
|
|
||||||
|
|
||||||
|
switch (op) {
|
||||||
case PDEV_BUS_OP_REMOVE_DEV:
|
case PDEV_BUS_OP_REMOVE_DEV:
|
||||||
goldfish_pdev_remove();
|
goldfish_pdev_remove();
|
||||||
|
ret = IRQ_HANDLED;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case PDEV_BUS_OP_ADD_DEV:
|
case PDEV_BUS_OP_ADD_DEV:
|
||||||
goldfish_new_pdev();
|
goldfish_new_pdev();
|
||||||
break;
|
|
||||||
}
|
|
||||||
ret = IRQ_HANDLED;
|
ret = IRQ_HANDLED;
|
||||||
}
|
break;
|
||||||
|
|
||||||
|
case PDEV_BUS_OP_DONE:
|
||||||
|
default:
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static int goldfish_pdev_bus_probe(struct platform_device *pdev)
|
static int goldfish_pdev_bus_probe(struct platform_device *pdev)
|
||||||
{
|
{
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue