Char/Misc patches for 4.6-rc1
Here is the big char/misc driver update for 4.6-rc1. The majority of the patches here is hwtracing and some new mic drivers, but there's a lot of other driver updates as well. Full details in the shortlog. All have been in linux-next for a while with no reported issues. Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> -----BEGIN PGP SIGNATURE----- Version: GnuPG v2 iEYEABECAAYFAlbp9IcACgkQMUfUDdst+ykyJgCeLTC2QNGrh51kiJglkVJ0yD36 q4MAn0NkvSX2+iv5Jq8MaX6UQoRa4Nun =MNjR -----END PGP SIGNATURE----- Merge tag 'char-misc-4.6-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc Pull char/misc updates from Greg KH: "Here is the big char/misc driver update for 4.6-rc1. The majority of the patches here is hwtracing and some new mic drivers, but there's a lot of other driver updates as well. Full details in the shortlog. All have been in linux-next for a while with no reported issues" * tag 'char-misc-4.6-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc: (238 commits) goldfish: Fix build error of missing ioremap on UM nvmem: mediatek: Fix later provider initialization nvmem: imx-ocotp: Fix return value of imx_ocotp_read nvmem: Fix dependencies for !HAS_IOMEM archs char: genrtc: replace blacklist with whitelist drivers/hwtracing: make coresight-etm-perf.c explicitly non-modular drivers: char: mem: fix IS_ERROR_VALUE usage char: xillybus: Fix internal data structure initialization pch_phub: return -ENODATA if ROM can't be mapped Drivers: hv: vmbus: Support kexec on ws2012 r2 and above Drivers: hv: vmbus: Support handling messages on multiple CPUs Drivers: hv: utils: Remove util transport handler from list if registration fails Drivers: hv: util: Pass the channel information during the init call Drivers: hv: vmbus: avoid unneeded compiler optimizations in vmbus_wait_for_unload() Drivers: hv: vmbus: remove code duplication in message handling Drivers: hv: vmbus: avoid wait_for_completion() on crash Drivers: hv: vmbus: don't loose HVMSG_TIMER_EXPIRED messages misc: at24: replace memory_accessor with nvmem_device_read eeprom: 93xx46: extend driver to plug into the NVMEM framework eeprom: at25: extend driver to plug into the NVMEM framework ...
This commit is contained in:
commit
8eee93e257
|
@ -27,3 +27,17 @@ Description: The mapping of which primary/sub channels are bound to which
|
|||
Virtual Processors.
|
||||
Format: <channel's child_relid:the bound cpu's number>
|
||||
Users: tools/hv/lsvmbus
|
||||
|
||||
What: /sys/bus/vmbus/devices/vmbus_*/device
|
||||
Date: Dec. 2015
|
||||
KernelVersion: 4.5
|
||||
Contact: K. Y. Srinivasan <kys@microsoft.com>
|
||||
Description: The 16 bit device ID of the device
|
||||
Users: tools/hv/lsvmbus and user level RDMA libraries
|
||||
|
||||
What: /sys/bus/vmbus/devices/vmbus_*/vendor
|
||||
Date: Dec. 2015
|
||||
KernelVersion: 4.5
|
||||
Contact: K. Y. Srinivasan <kys@microsoft.com>
|
||||
Description: The 16 bit vendor ID of the device
|
||||
Users: tools/hv/lsvmbus and user level RDMA libraries
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
Android Goldfish QEMU Pipe
|
||||
|
||||
Andorid pipe virtual device generated by android emulator.
|
||||
|
||||
Required properties:
|
||||
|
||||
- compatible : should contain "google,android-pipe" to match emulator
|
||||
- reg : <registers mapping>
|
||||
- interrupts : <interrupt mapping>
|
||||
|
||||
Example:
|
||||
|
||||
android_pipe@a010000 {
|
||||
compatible = "google,android-pipe";
|
||||
reg = <ff018000 0x2000>;
|
||||
interrupts = <0x12>;
|
||||
};
|
|
@ -0,0 +1,25 @@
|
|||
EEPROMs (SPI) compatible with Microchip Technology 93xx46 family.
|
||||
|
||||
Required properties:
|
||||
- compatible : shall be one of:
|
||||
"atmel,at93c46d"
|
||||
"eeprom-93xx46"
|
||||
- data-size : number of data bits per word (either 8 or 16)
|
||||
|
||||
Optional properties:
|
||||
- read-only : parameter-less property which disables writes to the EEPROM
|
||||
- select-gpios : if present, specifies the GPIO that will be asserted prior to
|
||||
each access to the EEPROM (e.g. for SPI bus multiplexing)
|
||||
|
||||
Property rules described in Documentation/devicetree/bindings/spi/spi-bus.txt
|
||||
apply. In particular, "reg" and "spi-max-frequency" properties must be given.
|
||||
|
||||
Example:
|
||||
eeprom@0 {
|
||||
compatible = "eeprom-93xx46";
|
||||
reg = <0>;
|
||||
spi-max-frequency = <1000000>;
|
||||
spi-cs-high;
|
||||
data-size = <8>;
|
||||
select-gpios = <&gpio4 4 GPIO_ACTIVE_HIGH>;
|
||||
};
|
|
@ -0,0 +1,28 @@
|
|||
* NXP LPC18xx EEPROM memory NVMEM driver
|
||||
|
||||
Required properties:
|
||||
- compatible: Should be "nxp,lpc1857-eeprom"
|
||||
- reg: Must contain an entry with the physical base address and length
|
||||
for each entry in reg-names.
|
||||
- reg-names: Must include the following entries.
|
||||
- reg: EEPROM registers.
|
||||
- mem: EEPROM address space.
|
||||
- clocks: Must contain an entry for each entry in clock-names.
|
||||
- clock-names: Must include the following entries.
|
||||
- eeprom: EEPROM operating clock.
|
||||
- resets: Should contain a reference to the reset controller asserting
|
||||
the EEPROM in reset.
|
||||
- interrupts: Should contain EEPROM interrupt.
|
||||
|
||||
Example:
|
||||
|
||||
eeprom: eeprom@4000e000 {
|
||||
compatible = "nxp,lpc1857-eeprom";
|
||||
reg = <0x4000e000 0x1000>,
|
||||
<0x20040000 0x4000>;
|
||||
reg-names = "reg", "mem";
|
||||
clocks = <&ccu1 CLK_CPU_EEPROM>;
|
||||
clock-names = "eeprom";
|
||||
resets = <&rgu 27>;
|
||||
interrupts = <4>;
|
||||
};
|
|
@ -0,0 +1,36 @@
|
|||
= Mediatek MTK-EFUSE device tree bindings =
|
||||
|
||||
This binding is intended to represent MTK-EFUSE which is found in most Mediatek SOCs.
|
||||
|
||||
Required properties:
|
||||
- compatible: should be "mediatek,mt8173-efuse" or "mediatek,efuse"
|
||||
- reg: Should contain registers location and length
|
||||
|
||||
= Data cells =
|
||||
Are child nodes of MTK-EFUSE, bindings of which as described in
|
||||
bindings/nvmem/nvmem.txt
|
||||
|
||||
Example:
|
||||
|
||||
efuse: efuse@10206000 {
|
||||
compatible = "mediatek,mt8173-efuse";
|
||||
reg = <0 0x10206000 0 0x1000>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
|
||||
/* Data cells */
|
||||
thermal_calibration: calib@528 {
|
||||
reg = <0x528 0xc>;
|
||||
};
|
||||
};
|
||||
|
||||
= Data consumers =
|
||||
Are device nodes which consume nvmem data cells.
|
||||
|
||||
For example:
|
||||
|
||||
thermal {
|
||||
...
|
||||
nvmem-cells = <&thermal_calibration>;
|
||||
nvmem-cell-names = "calibration";
|
||||
};
|
|
@ -12,10 +12,19 @@ for the X100 devices.
|
|||
|
||||
Since it is a PCIe card, it does not have the ability to host hardware
|
||||
devices for networking, storage and console. We provide these devices
|
||||
on X100 coprocessors thus enabling a self-bootable equivalent environment
|
||||
for applications. A key benefit of our solution is that it leverages
|
||||
the standard virtio framework for network, disk and console devices,
|
||||
though in our case the virtio framework is used across a PCIe bus.
|
||||
on X100 coprocessors thus enabling a self-bootable equivalent
|
||||
environment for applications. A key benefit of our solution is that it
|
||||
leverages the standard virtio framework for network, disk and console
|
||||
devices, though in our case the virtio framework is used across a PCIe
|
||||
bus. A Virtio Over PCIe (VOP) driver allows creating user space
|
||||
backends or devices on the host which are used to probe virtio drivers
|
||||
for these devices on the MIC card. The existing VRINGH infrastructure
|
||||
in the kernel is used to access virtio rings from the host. The card
|
||||
VOP driver allows card virtio drivers to communicate with their user
|
||||
space backends on the host via a device page. Ring 3 apps on the host
|
||||
can add, remove and configure virtio devices. A thin MIC specific
|
||||
virtio_config_ops is implemented which is borrowed heavily from
|
||||
previous similar implementations in lguest and s390.
|
||||
|
||||
MIC PCIe card has a dma controller with 8 channels. These channels are
|
||||
shared between the host s/w and the card s/w. 0 to 3 are used by host
|
||||
|
@ -38,7 +47,6 @@ single threaded performance for the host compared to MIC, the ability of
|
|||
the host to initiate DMA's to/from the card using the MIC DMA engine and
|
||||
the fact that the virtio block storage backend can only be on the host.
|
||||
|
||||
|
|
||||
+----------+ | +----------+
|
||||
| Card OS | | | Host OS |
|
||||
+----------+ | +----------+
|
||||
|
@ -47,27 +55,25 @@ the fact that the virtio block storage backend can only be on the host.
|
|||
| Virtio| |Virtio | |Virtio| | |Virtio | |Virtio | |Virtio |
|
||||
| Net | |Console | |Block | | |Net | |Console | |Block |
|
||||
| Driver| |Driver | |Driver| | |backend | |backend | |backend |
|
||||
+-------+ +--------+ +------+ | +---------+ +--------+ +--------+
|
||||
+---+---+ +---+----+ +--+---+ | +---------+ +----+---+ +--------+
|
||||
| | | | | | |
|
||||
| | | |User | | |
|
||||
| | | |------|------------|---------|-------
|
||||
+-------------------+ |Kernel +--------------------------+
|
||||
| | | Virtio over PCIe IOCTLs |
|
||||
| | +--------------------------+
|
||||
+-----------+ | | | +-----------+
|
||||
| MIC DMA | | +------+ | +------+ +------+ | | MIC DMA |
|
||||
| Driver | | | SCIF | | | SCIF | | COSM | | | Driver |
|
||||
+-----------+ | +------+ | +------+ +--+---+ | +-----------+
|
||||
| | | | | | | |
|
||||
+---------------+ | +------+ | +--+---+ +--+---+ | +----------------+
|
||||
|MIC virtual Bus| | |SCIF | | |SCIF | | COSM | | |MIC virtual Bus |
|
||||
+---------------+ | |HW Bus| | |HW Bus| | Bus | | +----------------+
|
||||
| | +------+ | +--+---+ +------+ | |
|
||||
| | | | | | | |
|
||||
| +-----------+---+ | | | +---------------+ |
|
||||
| |Intel MIC | | | | |Intel MIC | |
|
||||
+---|Card Driver | | | | |Host Driver | |
|
||||
+------------+--------+ | +----+---------------+-----+
|
||||
| | | |------|------------|--+------|-------
|
||||
+---------+---------+ |Kernel |
|
||||
| | |
|
||||
+---------+ +---+----+ +------+ | +------+ +------+ +--+---+ +-------+
|
||||
|MIC DMA | | VOP | | SCIF | | | SCIF | | COSM | | VOP | |MIC DMA|
|
||||
+---+-----+ +---+----+ +--+---+ | +--+---+ +--+---+ +------+ +----+--+
|
||||
| | | | | | |
|
||||
+---+-----+ +---+----+ +--+---+ | +--+---+ +--+---+ +------+ +----+--+
|
||||
|MIC | | VOP | |SCIF | | |SCIF | | COSM | | VOP | | MIC |
|
||||
|HW Bus | | HW Bus| |HW Bus| | |HW Bus| | Bus | |HW Bus| |HW Bus |
|
||||
+---------+ +--------+ +--+---+ | +--+---+ +------+ +------+ +-------+
|
||||
| | | | | | |
|
||||
| +-----------+--+ | | | +---------------+ |
|
||||
| |Intel MIC | | | | |Intel MIC | |
|
||||
| |Card Driver | | | | |Host Driver | |
|
||||
+---+--------------+------+ | +----+---------------+-----+
|
||||
| | |
|
||||
+-------------------------------------------------------------+
|
||||
| |
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
|
||||
exec=/usr/sbin/mpssd
|
||||
sysfs="/sys/class/mic"
|
||||
mic_modules="mic_host mic_x100_dma scif"
|
||||
mic_modules="mic_host mic_x100_dma scif vop"
|
||||
|
||||
start()
|
||||
{
|
||||
|
|
|
@ -926,7 +926,7 @@ add_virtio_device(struct mic_info *mic, struct mic_device_desc *dd)
|
|||
char path[PATH_MAX];
|
||||
int fd, err;
|
||||
|
||||
snprintf(path, PATH_MAX, "/dev/mic%d", mic->id);
|
||||
snprintf(path, PATH_MAX, "/dev/vop_virtio%d", mic->id);
|
||||
fd = open(path, O_RDWR);
|
||||
if (fd < 0) {
|
||||
mpsslog("Could not open %s %s\n", path, strerror(errno));
|
||||
|
|
|
@ -231,15 +231,15 @@ IT knows when a platform crashes even when there is a hard failure on the host.
|
|||
The Intel AMT Watchdog is composed of two parts:
|
||||
1) Firmware feature - receives the heartbeats
|
||||
and sends an event when the heartbeats stop.
|
||||
2) Intel MEI driver - connects to the watchdog feature, configures the
|
||||
watchdog and sends the heartbeats.
|
||||
2) Intel MEI iAMT watchdog driver - connects to the watchdog feature,
|
||||
configures the watchdog and sends the heartbeats.
|
||||
|
||||
The Intel MEI driver uses the kernel watchdog API to configure the Intel AMT
|
||||
Watchdog and to send heartbeats to it. The default timeout of the
|
||||
The Intel iAMT watchdog MEI driver uses the kernel watchdog API to configure
|
||||
the Intel AMT Watchdog and to send heartbeats to it. The default timeout of the
|
||||
watchdog is 120 seconds.
|
||||
|
||||
If the Intel AMT Watchdog feature does not exist (i.e. the connection failed),
|
||||
the Intel MEI driver will disable the sending of heartbeats.
|
||||
If the Intel AMT is not enabled in the firmware then the watchdog client won't enumerate
|
||||
on the me client bus and watchdog devices won't be exposed.
|
||||
|
||||
|
||||
Supported Chipsets
|
||||
|
|
|
@ -5765,6 +5765,7 @@ S: Supported
|
|||
F: include/uapi/linux/mei.h
|
||||
F: include/linux/mei_cl_bus.h
|
||||
F: drivers/misc/mei/*
|
||||
F: drivers/watchdog/mei_wdt.c
|
||||
F: Documentation/misc-devices/mei/*
|
||||
|
||||
INTEL MIC DRIVERS (mic)
|
||||
|
@ -6598,6 +6599,11 @@ F: samples/livepatch/
|
|||
L: live-patching@vger.kernel.org
|
||||
T: git git://git.kernel.org/pub/scm/linux/kernel/git/jikos/livepatching.git
|
||||
|
||||
LINUX KERNEL DUMP TEST MODULE (LKDTM)
|
||||
M: Kees Cook <keescook@chromium.org>
|
||||
S: Maintained
|
||||
F: drivers/misc/lkdtm.c
|
||||
|
||||
LLC (802.2)
|
||||
M: Arnaldo Carvalho de Melo <acme@ghostprotocols.net>
|
||||
S: Maintained
|
||||
|
|
|
@ -562,8 +562,7 @@
|
|||
extcon_usb2: tps659038_usb {
|
||||
compatible = "ti,palmas-usb-vid";
|
||||
ti,enable-vbus-detection;
|
||||
ti,enable-id-detection;
|
||||
id-gpios = <&gpio7 24 GPIO_ACTIVE_HIGH>;
|
||||
vbus-gpio = <&gpio4 21 GPIO_ACTIVE_HIGH>;
|
||||
};
|
||||
|
||||
};
|
||||
|
|
|
@ -115,13 +115,14 @@ static void mityomapl138_cpufreq_init(const char *partnum)
|
|||
static void mityomapl138_cpufreq_init(const char *partnum) { }
|
||||
#endif
|
||||
|
||||
static void read_factory_config(struct memory_accessor *a, void *context)
|
||||
static void read_factory_config(struct nvmem_device *nvmem, void *context)
|
||||
{
|
||||
int ret;
|
||||
const char *partnum = NULL;
|
||||
struct davinci_soc_info *soc_info = &davinci_soc_info;
|
||||
|
||||
ret = a->read(a, (char *)&factory_config, 0, sizeof(factory_config));
|
||||
ret = nvmem_device_read(nvmem, 0, sizeof(factory_config),
|
||||
&factory_config);
|
||||
if (ret != sizeof(struct factory_config)) {
|
||||
pr_warn("Read Factory Config Failed: %d\n", ret);
|
||||
goto bad_config;
|
||||
|
|
|
@ -28,13 +28,13 @@ EXPORT_SYMBOL(davinci_soc_info);
|
|||
void __iomem *davinci_intc_base;
|
||||
int davinci_intc_type;
|
||||
|
||||
void davinci_get_mac_addr(struct memory_accessor *mem_acc, void *context)
|
||||
void davinci_get_mac_addr(struct nvmem_device *nvmem, void *context)
|
||||
{
|
||||
char *mac_addr = davinci_soc_info.emac_pdata->mac_addr;
|
||||
off_t offset = (off_t)context;
|
||||
|
||||
/* Read MAC addr from EEPROM */
|
||||
if (mem_acc->read(mem_acc, mac_addr, offset, ETH_ALEN) == ETH_ALEN)
|
||||
if (nvmem_device_read(nvmem, offset, ETH_ALEN, mac_addr) == ETH_ALEN)
|
||||
pr_info("Read MAC addr from EEPROM: %pM\n", mac_addr);
|
||||
}
|
||||
|
||||
|
|
|
@ -1321,6 +1321,7 @@ static void binder_transaction(struct binder_proc *proc,
|
|||
struct binder_transaction *t;
|
||||
struct binder_work *tcomplete;
|
||||
binder_size_t *offp, *off_end;
|
||||
binder_size_t off_min;
|
||||
struct binder_proc *target_proc;
|
||||
struct binder_thread *target_thread = NULL;
|
||||
struct binder_node *target_node = NULL;
|
||||
|
@ -1522,18 +1523,24 @@ static void binder_transaction(struct binder_proc *proc,
|
|||
goto err_bad_offset;
|
||||
}
|
||||
off_end = (void *)offp + tr->offsets_size;
|
||||
off_min = 0;
|
||||
for (; offp < off_end; offp++) {
|
||||
struct flat_binder_object *fp;
|
||||
|
||||
if (*offp > t->buffer->data_size - sizeof(*fp) ||
|
||||
*offp < off_min ||
|
||||
t->buffer->data_size < sizeof(*fp) ||
|
||||
!IS_ALIGNED(*offp, sizeof(u32))) {
|
||||
binder_user_error("%d:%d got transaction with invalid offset, %lld\n",
|
||||
proc->pid, thread->pid, (u64)*offp);
|
||||
binder_user_error("%d:%d got transaction with invalid offset, %lld (min %lld, max %lld)\n",
|
||||
proc->pid, thread->pid, (u64)*offp,
|
||||
(u64)off_min,
|
||||
(u64)(t->buffer->data_size -
|
||||
sizeof(*fp)));
|
||||
return_error = BR_FAILED_REPLY;
|
||||
goto err_bad_offset;
|
||||
}
|
||||
fp = (struct flat_binder_object *)(t->buffer->data + *offp);
|
||||
off_min = *offp + sizeof(struct flat_binder_object);
|
||||
switch (fp->type) {
|
||||
case BINDER_TYPE_BINDER:
|
||||
case BINDER_TYPE_WEAK_BINDER: {
|
||||
|
@ -3593,13 +3600,24 @@ static int binder_transactions_show(struct seq_file *m, void *unused)
|
|||
|
||||
static int binder_proc_show(struct seq_file *m, void *unused)
|
||||
{
|
||||
struct binder_proc *itr;
|
||||
struct binder_proc *proc = m->private;
|
||||
int do_lock = !binder_debug_no_lock;
|
||||
bool valid_proc = false;
|
||||
|
||||
if (do_lock)
|
||||
binder_lock(__func__);
|
||||
seq_puts(m, "binder proc state:\n");
|
||||
print_binder_proc(m, proc, 1);
|
||||
|
||||
hlist_for_each_entry(itr, &binder_procs, proc_node) {
|
||||
if (itr == proc) {
|
||||
valid_proc = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (valid_proc) {
|
||||
seq_puts(m, "binder proc state:\n");
|
||||
print_binder_proc(m, proc, 1);
|
||||
}
|
||||
if (do_lock)
|
||||
binder_unlock(__func__);
|
||||
return 0;
|
||||
|
|
|
@ -258,7 +258,7 @@ static void __fw_free_buf(struct kref *ref)
|
|||
vunmap(buf->data);
|
||||
for (i = 0; i < buf->nr_pages; i++)
|
||||
__free_page(buf->pages[i]);
|
||||
kfree(buf->pages);
|
||||
vfree(buf->pages);
|
||||
} else
|
||||
#endif
|
||||
vfree(buf->data);
|
||||
|
@ -635,7 +635,7 @@ static ssize_t firmware_loading_store(struct device *dev,
|
|||
if (!test_bit(FW_STATUS_DONE, &fw_buf->status)) {
|
||||
for (i = 0; i < fw_buf->nr_pages; i++)
|
||||
__free_page(fw_buf->pages[i]);
|
||||
kfree(fw_buf->pages);
|
||||
vfree(fw_buf->pages);
|
||||
fw_buf->pages = NULL;
|
||||
fw_buf->page_array_size = 0;
|
||||
fw_buf->nr_pages = 0;
|
||||
|
@ -746,8 +746,7 @@ static int fw_realloc_buffer(struct firmware_priv *fw_priv, int min_size)
|
|||
buf->page_array_size * 2);
|
||||
struct page **new_pages;
|
||||
|
||||
new_pages = kmalloc(new_array_size * sizeof(void *),
|
||||
GFP_KERNEL);
|
||||
new_pages = vmalloc(new_array_size * sizeof(void *));
|
||||
if (!new_pages) {
|
||||
fw_load_abort(fw_priv);
|
||||
return -ENOMEM;
|
||||
|
@ -756,7 +755,7 @@ static int fw_realloc_buffer(struct firmware_priv *fw_priv, int min_size)
|
|||
buf->page_array_size * sizeof(void *));
|
||||
memset(&new_pages[buf->page_array_size], 0, sizeof(void *) *
|
||||
(new_array_size - buf->page_array_size));
|
||||
kfree(buf->pages);
|
||||
vfree(buf->pages);
|
||||
buf->pages = new_pages;
|
||||
buf->page_array_size = new_array_size;
|
||||
}
|
||||
|
|
|
@ -328,7 +328,8 @@ config JS_RTC
|
|||
|
||||
config GEN_RTC
|
||||
tristate "Generic /dev/rtc emulation"
|
||||
depends on RTC!=y && !IA64 && !ARM && !M32R && !MIPS && !SPARC && !FRV && !S390 && !SUPERH && !AVR32 && !BLACKFIN && !UML
|
||||
depends on RTC!=y
|
||||
depends on ALPHA || M68K || MN10300 || PARISC || PPC || X86
|
||||
---help---
|
||||
If you say Y here and create a character special file /dev/rtc with
|
||||
major number 10 and minor number 135 using mknod ("man mknod"), you
|
||||
|
|
|
@ -695,7 +695,7 @@ static loff_t memory_lseek(struct file *file, loff_t offset, int orig)
|
|||
offset += file->f_pos;
|
||||
case SEEK_SET:
|
||||
/* to avoid userland mistaking f_pos=-9 as -EBADF=-9 */
|
||||
if (IS_ERR_VALUE((unsigned long long)offset)) {
|
||||
if ((unsigned long long)offset >= -MAX_ERRNO) {
|
||||
ret = -EOVERFLOW;
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -496,12 +496,12 @@ static void pc_set_checksum(void)
|
|||
|
||||
#ifdef CONFIG_PROC_FS
|
||||
|
||||
static char *floppy_types[] = {
|
||||
static const char * const floppy_types[] = {
|
||||
"none", "5.25'' 360k", "5.25'' 1.2M", "3.5'' 720k", "3.5'' 1.44M",
|
||||
"3.5'' 2.88M", "3.5'' 2.88M"
|
||||
};
|
||||
|
||||
static char *gfx_types[] = {
|
||||
static const char * const gfx_types[] = {
|
||||
"EGA, VGA, ... (with BIOS)",
|
||||
"CGA (40 cols)",
|
||||
"CGA (80 cols)",
|
||||
|
@ -602,7 +602,7 @@ static void atari_set_checksum(void)
|
|||
|
||||
static struct {
|
||||
unsigned char val;
|
||||
char *name;
|
||||
const char *name;
|
||||
} boot_prefs[] = {
|
||||
{ 0x80, "TOS" },
|
||||
{ 0x40, "ASV" },
|
||||
|
@ -611,7 +611,7 @@ static struct {
|
|||
{ 0x00, "unspecified" }
|
||||
};
|
||||
|
||||
static char *languages[] = {
|
||||
static const char * const languages[] = {
|
||||
"English (US)",
|
||||
"German",
|
||||
"French",
|
||||
|
@ -623,7 +623,7 @@ static char *languages[] = {
|
|||
"Swiss (German)"
|
||||
};
|
||||
|
||||
static char *dateformat[] = {
|
||||
static const char * const dateformat[] = {
|
||||
"MM%cDD%cYY",
|
||||
"DD%cMM%cYY",
|
||||
"YY%cMM%cDD",
|
||||
|
@ -634,7 +634,7 @@ static char *dateformat[] = {
|
|||
"7 (undefined)"
|
||||
};
|
||||
|
||||
static char *colors[] = {
|
||||
static const char * const colors[] = {
|
||||
"2", "4", "16", "256", "65536", "??", "??", "??"
|
||||
};
|
||||
|
||||
|
|
|
@ -129,10 +129,9 @@ static void button_consume_callbacks (int bpcount)
|
|||
|
||||
static void button_sequence_finished (unsigned long parameters)
|
||||
{
|
||||
#ifdef CONFIG_NWBUTTON_REBOOT /* Reboot using button is enabled */
|
||||
if (button_press_count == reboot_count)
|
||||
if (IS_ENABLED(CONFIG_NWBUTTON_REBOOT) &&
|
||||
button_press_count == reboot_count)
|
||||
kill_cad_pid(SIGINT, 1); /* Ask init to reboot us */
|
||||
#endif /* CONFIG_NWBUTTON_REBOOT */
|
||||
button_consume_callbacks (button_press_count);
|
||||
bcount = sprintf (button_output_buffer, "%d\n", button_press_count);
|
||||
button_press_count = 0; /* Reset the button press counter */
|
||||
|
|
|
@ -69,12 +69,13 @@
|
|||
#include <linux/ppdev.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/compat.h>
|
||||
|
||||
#define PP_VERSION "ppdev: user-space parallel port driver"
|
||||
#define CHRDEV "ppdev"
|
||||
|
||||
struct pp_struct {
|
||||
struct pardevice * pdev;
|
||||
struct pardevice *pdev;
|
||||
wait_queue_head_t irq_wait;
|
||||
atomic_t irqc;
|
||||
unsigned int flags;
|
||||
|
@ -98,18 +99,26 @@ struct pp_struct {
|
|||
#define ROUND_UP(x,y) (((x)+(y)-1)/(y))
|
||||
|
||||
static DEFINE_MUTEX(pp_do_mutex);
|
||||
static inline void pp_enable_irq (struct pp_struct *pp)
|
||||
|
||||
/* define fixed sized ioctl cmd for y2038 migration */
|
||||
#define PPGETTIME32 _IOR(PP_IOCTL, 0x95, s32[2])
|
||||
#define PPSETTIME32 _IOW(PP_IOCTL, 0x96, s32[2])
|
||||
#define PPGETTIME64 _IOR(PP_IOCTL, 0x95, s64[2])
|
||||
#define PPSETTIME64 _IOW(PP_IOCTL, 0x96, s64[2])
|
||||
|
||||
static inline void pp_enable_irq(struct pp_struct *pp)
|
||||
{
|
||||
struct parport *port = pp->pdev->port;
|
||||
port->ops->enable_irq (port);
|
||||
|
||||
port->ops->enable_irq(port);
|
||||
}
|
||||
|
||||
static ssize_t pp_read (struct file * file, char __user * buf, size_t count,
|
||||
loff_t * ppos)
|
||||
static ssize_t pp_read(struct file *file, char __user *buf, size_t count,
|
||||
loff_t *ppos)
|
||||
{
|
||||
unsigned int minor = iminor(file_inode(file));
|
||||
struct pp_struct *pp = file->private_data;
|
||||
char * kbuffer;
|
||||
char *kbuffer;
|
||||
ssize_t bytes_read = 0;
|
||||
struct parport *pport;
|
||||
int mode;
|
||||
|
@ -125,16 +134,15 @@ static ssize_t pp_read (struct file * file, char __user * buf, size_t count,
|
|||
return 0;
|
||||
|
||||
kbuffer = kmalloc(min_t(size_t, count, PP_BUFFER_SIZE), GFP_KERNEL);
|
||||
if (!kbuffer) {
|
||||
if (!kbuffer)
|
||||
return -ENOMEM;
|
||||
}
|
||||
pport = pp->pdev->port;
|
||||
mode = pport->ieee1284.mode & ~(IEEE1284_DEVICEID | IEEE1284_ADDR);
|
||||
|
||||
parport_set_timeout (pp->pdev,
|
||||
(file->f_flags & O_NONBLOCK) ?
|
||||
PARPORT_INACTIVITY_O_NONBLOCK :
|
||||
pp->default_inactivity);
|
||||
parport_set_timeout(pp->pdev,
|
||||
(file->f_flags & O_NONBLOCK) ?
|
||||
PARPORT_INACTIVITY_O_NONBLOCK :
|
||||
pp->default_inactivity);
|
||||
|
||||
while (bytes_read == 0) {
|
||||
ssize_t need = min_t(unsigned long, count, PP_BUFFER_SIZE);
|
||||
|
@ -144,20 +152,17 @@ static ssize_t pp_read (struct file * file, char __user * buf, size_t count,
|
|||
int flags = 0;
|
||||
size_t (*fn)(struct parport *, void *, size_t, int);
|
||||
|
||||
if (pp->flags & PP_W91284PIC) {
|
||||
if (pp->flags & PP_W91284PIC)
|
||||
flags |= PARPORT_W91284PIC;
|
||||
}
|
||||
if (pp->flags & PP_FASTREAD) {
|
||||
if (pp->flags & PP_FASTREAD)
|
||||
flags |= PARPORT_EPP_FAST;
|
||||
}
|
||||
if (pport->ieee1284.mode & IEEE1284_ADDR) {
|
||||
if (pport->ieee1284.mode & IEEE1284_ADDR)
|
||||
fn = pport->ops->epp_read_addr;
|
||||
} else {
|
||||
else
|
||||
fn = pport->ops->epp_read_data;
|
||||
}
|
||||
bytes_read = (*fn)(pport, kbuffer, need, flags);
|
||||
} else {
|
||||
bytes_read = parport_read (pport, kbuffer, need);
|
||||
bytes_read = parport_read(pport, kbuffer, need);
|
||||
}
|
||||
|
||||
if (bytes_read != 0)
|
||||
|
@ -168,7 +173,7 @@ static ssize_t pp_read (struct file * file, char __user * buf, size_t count,
|
|||
break;
|
||||
}
|
||||
|
||||
if (signal_pending (current)) {
|
||||
if (signal_pending(current)) {
|
||||
bytes_read = -ERESTARTSYS;
|
||||
break;
|
||||
}
|
||||
|
@ -176,22 +181,22 @@ static ssize_t pp_read (struct file * file, char __user * buf, size_t count,
|
|||
cond_resched();
|
||||
}
|
||||
|
||||
parport_set_timeout (pp->pdev, pp->default_inactivity);
|
||||
parport_set_timeout(pp->pdev, pp->default_inactivity);
|
||||
|
||||
if (bytes_read > 0 && copy_to_user (buf, kbuffer, bytes_read))
|
||||
if (bytes_read > 0 && copy_to_user(buf, kbuffer, bytes_read))
|
||||
bytes_read = -EFAULT;
|
||||
|
||||
kfree (kbuffer);
|
||||
pp_enable_irq (pp);
|
||||
kfree(kbuffer);
|
||||
pp_enable_irq(pp);
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
static ssize_t pp_write (struct file * file, const char __user * buf,
|
||||
size_t count, loff_t * ppos)
|
||||
static ssize_t pp_write(struct file *file, const char __user *buf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
unsigned int minor = iminor(file_inode(file));
|
||||
struct pp_struct *pp = file->private_data;
|
||||
char * kbuffer;
|
||||
char *kbuffer;
|
||||
ssize_t bytes_written = 0;
|
||||
ssize_t wrote;
|
||||
int mode;
|
||||
|
@ -204,21 +209,21 @@ static ssize_t pp_write (struct file * file, const char __user * buf,
|
|||
}
|
||||
|
||||
kbuffer = kmalloc(min_t(size_t, count, PP_BUFFER_SIZE), GFP_KERNEL);
|
||||
if (!kbuffer) {
|
||||
if (!kbuffer)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
pport = pp->pdev->port;
|
||||
mode = pport->ieee1284.mode & ~(IEEE1284_DEVICEID | IEEE1284_ADDR);
|
||||
|
||||
parport_set_timeout (pp->pdev,
|
||||
(file->f_flags & O_NONBLOCK) ?
|
||||
PARPORT_INACTIVITY_O_NONBLOCK :
|
||||
pp->default_inactivity);
|
||||
parport_set_timeout(pp->pdev,
|
||||
(file->f_flags & O_NONBLOCK) ?
|
||||
PARPORT_INACTIVITY_O_NONBLOCK :
|
||||
pp->default_inactivity);
|
||||
|
||||
while (bytes_written < count) {
|
||||
ssize_t n = min_t(unsigned long, count - bytes_written, PP_BUFFER_SIZE);
|
||||
|
||||
if (copy_from_user (kbuffer, buf + bytes_written, n)) {
|
||||
if (copy_from_user(kbuffer, buf + bytes_written, n)) {
|
||||
bytes_written = -EFAULT;
|
||||
break;
|
||||
}
|
||||
|
@ -226,20 +231,19 @@ static ssize_t pp_write (struct file * file, const char __user * buf,
|
|||
if ((pp->flags & PP_FASTWRITE) && (mode == IEEE1284_MODE_EPP)) {
|
||||
/* do a fast EPP write */
|
||||
if (pport->ieee1284.mode & IEEE1284_ADDR) {
|
||||
wrote = pport->ops->epp_write_addr (pport,
|
||||
wrote = pport->ops->epp_write_addr(pport,
|
||||
kbuffer, n, PARPORT_EPP_FAST);
|
||||
} else {
|
||||
wrote = pport->ops->epp_write_data (pport,
|
||||
wrote = pport->ops->epp_write_data(pport,
|
||||
kbuffer, n, PARPORT_EPP_FAST);
|
||||
}
|
||||
} else {
|
||||
wrote = parport_write (pp->pdev->port, kbuffer, n);
|
||||
wrote = parport_write(pp->pdev->port, kbuffer, n);
|
||||
}
|
||||
|
||||
if (wrote <= 0) {
|
||||
if (!bytes_written) {
|
||||
if (!bytes_written)
|
||||
bytes_written = wrote;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -251,67 +255,69 @@ static ssize_t pp_write (struct file * file, const char __user * buf,
|
|||
break;
|
||||
}
|
||||
|
||||
if (signal_pending (current))
|
||||
if (signal_pending(current))
|
||||
break;
|
||||
|
||||
cond_resched();
|
||||
}
|
||||
|
||||
parport_set_timeout (pp->pdev, pp->default_inactivity);
|
||||
parport_set_timeout(pp->pdev, pp->default_inactivity);
|
||||
|
||||
kfree (kbuffer);
|
||||
pp_enable_irq (pp);
|
||||
kfree(kbuffer);
|
||||
pp_enable_irq(pp);
|
||||
return bytes_written;
|
||||
}
|
||||
|
||||
static void pp_irq (void *private)
|
||||
static void pp_irq(void *private)
|
||||
{
|
||||
struct pp_struct *pp = private;
|
||||
|
||||
if (pp->irqresponse) {
|
||||
parport_write_control (pp->pdev->port, pp->irqctl);
|
||||
parport_write_control(pp->pdev->port, pp->irqctl);
|
||||
pp->irqresponse = 0;
|
||||
}
|
||||
|
||||
atomic_inc (&pp->irqc);
|
||||
wake_up_interruptible (&pp->irq_wait);
|
||||
atomic_inc(&pp->irqc);
|
||||
wake_up_interruptible(&pp->irq_wait);
|
||||
}
|
||||
|
||||
static int register_device (int minor, struct pp_struct *pp)
|
||||
static int register_device(int minor, struct pp_struct *pp)
|
||||
{
|
||||
struct parport *port;
|
||||
struct pardevice * pdev = NULL;
|
||||
struct pardevice *pdev = NULL;
|
||||
char *name;
|
||||
int fl;
|
||||
struct pardev_cb ppdev_cb;
|
||||
|
||||
name = kasprintf(GFP_KERNEL, CHRDEV "%x", minor);
|
||||
if (name == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
port = parport_find_number (minor);
|
||||
port = parport_find_number(minor);
|
||||
if (!port) {
|
||||
printk (KERN_WARNING "%s: no associated port!\n", name);
|
||||
kfree (name);
|
||||
printk(KERN_WARNING "%s: no associated port!\n", name);
|
||||
kfree(name);
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
fl = (pp->flags & PP_EXCL) ? PARPORT_FLAG_EXCL : 0;
|
||||
pdev = parport_register_device (port, name, NULL,
|
||||
NULL, pp_irq, fl, pp);
|
||||
parport_put_port (port);
|
||||
memset(&ppdev_cb, 0, sizeof(ppdev_cb));
|
||||
ppdev_cb.irq_func = pp_irq;
|
||||
ppdev_cb.flags = (pp->flags & PP_EXCL) ? PARPORT_FLAG_EXCL : 0;
|
||||
ppdev_cb.private = pp;
|
||||
pdev = parport_register_dev_model(port, name, &ppdev_cb, minor);
|
||||
parport_put_port(port);
|
||||
|
||||
if (!pdev) {
|
||||
printk (KERN_WARNING "%s: failed to register device!\n", name);
|
||||
kfree (name);
|
||||
printk(KERN_WARNING "%s: failed to register device!\n", name);
|
||||
kfree(name);
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
pp->pdev = pdev;
|
||||
pr_debug("%s: registered pardevice\n", name);
|
||||
dev_dbg(&pdev->dev, "registered pardevice\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static enum ieee1284_phase init_phase (int mode)
|
||||
static enum ieee1284_phase init_phase(int mode)
|
||||
{
|
||||
switch (mode & ~(IEEE1284_DEVICEID
|
||||
| IEEE1284_ADDR)) {
|
||||
|
@ -322,11 +328,27 @@ static enum ieee1284_phase init_phase (int mode)
|
|||
return IEEE1284_PH_FWD_IDLE;
|
||||
}
|
||||
|
||||
static int pp_set_timeout(struct pardevice *pdev, long tv_sec, int tv_usec)
|
||||
{
|
||||
long to_jiffies;
|
||||
|
||||
if ((tv_sec < 0) || (tv_usec < 0))
|
||||
return -EINVAL;
|
||||
|
||||
to_jiffies = usecs_to_jiffies(tv_usec);
|
||||
to_jiffies += tv_sec * HZ;
|
||||
if (to_jiffies <= 0)
|
||||
return -EINVAL;
|
||||
|
||||
pdev->timeout = to_jiffies;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pp_do_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
unsigned int minor = iminor(file_inode(file));
|
||||
struct pp_struct *pp = file->private_data;
|
||||
struct parport * port;
|
||||
struct parport *port;
|
||||
void __user *argp = (void __user *)arg;
|
||||
|
||||
/* First handle the cases that don't take arguments. */
|
||||
|
@ -337,19 +359,19 @@ static int pp_do_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
|||
int ret;
|
||||
|
||||
if (pp->flags & PP_CLAIMED) {
|
||||
pr_debug(CHRDEV "%x: you've already got it!\n", minor);
|
||||
dev_dbg(&pp->pdev->dev, "you've already got it!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Deferred device registration. */
|
||||
if (!pp->pdev) {
|
||||
int err = register_device (minor, pp);
|
||||
if (err) {
|
||||
int err = register_device(minor, pp);
|
||||
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
ret = parport_claim_or_block (pp->pdev);
|
||||
ret = parport_claim_or_block(pp->pdev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
|
@ -357,7 +379,7 @@ static int pp_do_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
|||
|
||||
/* For interrupt-reporting to work, we need to be
|
||||
* informed of each interrupt. */
|
||||
pp_enable_irq (pp);
|
||||
pp_enable_irq(pp);
|
||||
|
||||
/* We may need to fix up the state machine. */
|
||||
info = &pp->pdev->port->ieee1284;
|
||||
|
@ -365,15 +387,15 @@ static int pp_do_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
|||
pp->saved_state.phase = info->phase;
|
||||
info->mode = pp->state.mode;
|
||||
info->phase = pp->state.phase;
|
||||
pp->default_inactivity = parport_set_timeout (pp->pdev, 0);
|
||||
parport_set_timeout (pp->pdev, pp->default_inactivity);
|
||||
pp->default_inactivity = parport_set_timeout(pp->pdev, 0);
|
||||
parport_set_timeout(pp->pdev, pp->default_inactivity);
|
||||
|
||||
return 0;
|
||||
}
|
||||
case PPEXCL:
|
||||
if (pp->pdev) {
|
||||
pr_debug(CHRDEV "%x: too late for PPEXCL; "
|
||||
"already registered\n", minor);
|
||||
dev_dbg(&pp->pdev->dev,
|
||||
"too late for PPEXCL; already registered\n");
|
||||
if (pp->flags & PP_EXCL)
|
||||
/* But it's not really an error. */
|
||||
return 0;
|
||||
|
@ -388,11 +410,12 @@ static int pp_do_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
|||
case PPSETMODE:
|
||||
{
|
||||
int mode;
|
||||
if (copy_from_user (&mode, argp, sizeof (mode)))
|
||||
|
||||
if (copy_from_user(&mode, argp, sizeof(mode)))
|
||||
return -EFAULT;
|
||||
/* FIXME: validate mode */
|
||||
pp->state.mode = mode;
|
||||
pp->state.phase = init_phase (mode);
|
||||
pp->state.phase = init_phase(mode);
|
||||
|
||||
if (pp->flags & PP_CLAIMED) {
|
||||
pp->pdev->port->ieee1284.mode = mode;
|
||||
|
@ -405,28 +428,27 @@ static int pp_do_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
|||
{
|
||||
int mode;
|
||||
|
||||
if (pp->flags & PP_CLAIMED) {
|
||||
if (pp->flags & PP_CLAIMED)
|
||||
mode = pp->pdev->port->ieee1284.mode;
|
||||
} else {
|
||||
else
|
||||
mode = pp->state.mode;
|
||||
}
|
||||
if (copy_to_user (argp, &mode, sizeof (mode))) {
|
||||
|
||||
if (copy_to_user(argp, &mode, sizeof(mode)))
|
||||
return -EFAULT;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
case PPSETPHASE:
|
||||
{
|
||||
int phase;
|
||||
if (copy_from_user (&phase, argp, sizeof (phase))) {
|
||||
|
||||
if (copy_from_user(&phase, argp, sizeof(phase)))
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
/* FIXME: validate phase */
|
||||
pp->state.phase = phase;
|
||||
|
||||
if (pp->flags & PP_CLAIMED) {
|
||||
if (pp->flags & PP_CLAIMED)
|
||||
pp->pdev->port->ieee1284.phase = phase;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -434,38 +456,34 @@ static int pp_do_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
|||
{
|
||||
int phase;
|
||||
|
||||
if (pp->flags & PP_CLAIMED) {
|
||||
if (pp->flags & PP_CLAIMED)
|
||||
phase = pp->pdev->port->ieee1284.phase;
|
||||
} else {
|
||||
else
|
||||
phase = pp->state.phase;
|
||||
}
|
||||
if (copy_to_user (argp, &phase, sizeof (phase))) {
|
||||
if (copy_to_user(argp, &phase, sizeof(phase)))
|
||||
return -EFAULT;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
case PPGETMODES:
|
||||
{
|
||||
unsigned int modes;
|
||||
|
||||
port = parport_find_number (minor);
|
||||
port = parport_find_number(minor);
|
||||
if (!port)
|
||||
return -ENODEV;
|
||||
|
||||
modes = port->modes;
|
||||
parport_put_port(port);
|
||||
if (copy_to_user (argp, &modes, sizeof (modes))) {
|
||||
if (copy_to_user(argp, &modes, sizeof(modes)))
|
||||
return -EFAULT;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
case PPSETFLAGS:
|
||||
{
|
||||
int uflags;
|
||||
|
||||
if (copy_from_user (&uflags, argp, sizeof (uflags))) {
|
||||
if (copy_from_user(&uflags, argp, sizeof(uflags)))
|
||||
return -EFAULT;
|
||||
}
|
||||
pp->flags &= ~PP_FLAGMASK;
|
||||
pp->flags |= (uflags & PP_FLAGMASK);
|
||||
return 0;
|
||||
|
@ -475,9 +493,8 @@ static int pp_do_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
|||
int uflags;
|
||||
|
||||
uflags = pp->flags & PP_FLAGMASK;
|
||||
if (copy_to_user (argp, &uflags, sizeof (uflags))) {
|
||||
if (copy_to_user(argp, &uflags, sizeof(uflags)))
|
||||
return -EFAULT;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
} /* end switch() */
|
||||
|
@ -495,27 +512,28 @@ static int pp_do_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
|||
unsigned char reg;
|
||||
unsigned char mask;
|
||||
int mode;
|
||||
s32 time32[2];
|
||||
s64 time64[2];
|
||||
struct timespec64 ts;
|
||||
int ret;
|
||||
struct timeval par_timeout;
|
||||
long to_jiffies;
|
||||
|
||||
case PPRSTATUS:
|
||||
reg = parport_read_status (port);
|
||||
if (copy_to_user (argp, ®, sizeof (reg)))
|
||||
reg = parport_read_status(port);
|
||||
if (copy_to_user(argp, ®, sizeof(reg)))
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
case PPRDATA:
|
||||
reg = parport_read_data (port);
|
||||
if (copy_to_user (argp, ®, sizeof (reg)))
|
||||
reg = parport_read_data(port);
|
||||
if (copy_to_user(argp, ®, sizeof(reg)))
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
case PPRCONTROL:
|
||||
reg = parport_read_control (port);
|
||||
if (copy_to_user (argp, ®, sizeof (reg)))
|
||||
reg = parport_read_control(port);
|
||||
if (copy_to_user(argp, ®, sizeof(reg)))
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
case PPYIELD:
|
||||
parport_yield_blocking (pp->pdev);
|
||||
parport_yield_blocking(pp->pdev);
|
||||
return 0;
|
||||
|
||||
case PPRELEASE:
|
||||
|
@ -525,45 +543,45 @@ static int pp_do_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
|||
pp->state.phase = info->phase;
|
||||
info->mode = pp->saved_state.mode;
|
||||
info->phase = pp->saved_state.phase;
|
||||
parport_release (pp->pdev);
|
||||
parport_release(pp->pdev);
|
||||
pp->flags &= ~PP_CLAIMED;
|
||||
return 0;
|
||||
|
||||
case PPWCONTROL:
|
||||
if (copy_from_user (®, argp, sizeof (reg)))
|
||||
if (copy_from_user(®, argp, sizeof(reg)))
|
||||
return -EFAULT;
|
||||
parport_write_control (port, reg);
|
||||
parport_write_control(port, reg);
|
||||
return 0;
|
||||
|
||||
case PPWDATA:
|
||||
if (copy_from_user (®, argp, sizeof (reg)))
|
||||
if (copy_from_user(®, argp, sizeof(reg)))
|
||||
return -EFAULT;
|
||||
parport_write_data (port, reg);
|
||||
parport_write_data(port, reg);
|
||||
return 0;
|
||||
|
||||
case PPFCONTROL:
|
||||
if (copy_from_user (&mask, argp,
|
||||
sizeof (mask)))
|
||||
if (copy_from_user(&mask, argp,
|
||||
sizeof(mask)))
|
||||
return -EFAULT;
|
||||
if (copy_from_user (®, 1 + (unsigned char __user *) arg,
|
||||
sizeof (reg)))
|
||||
if (copy_from_user(®, 1 + (unsigned char __user *) arg,
|
||||
sizeof(reg)))
|
||||
return -EFAULT;
|
||||
parport_frob_control (port, mask, reg);
|
||||
parport_frob_control(port, mask, reg);
|
||||
return 0;
|
||||
|
||||
case PPDATADIR:
|
||||
if (copy_from_user (&mode, argp, sizeof (mode)))
|
||||
if (copy_from_user(&mode, argp, sizeof(mode)))
|
||||
return -EFAULT;
|
||||
if (mode)
|
||||
port->ops->data_reverse (port);
|
||||
port->ops->data_reverse(port);
|
||||
else
|
||||
port->ops->data_forward (port);
|
||||
port->ops->data_forward(port);
|
||||
return 0;
|
||||
|
||||
case PPNEGOT:
|
||||
if (copy_from_user (&mode, argp, sizeof (mode)))
|
||||
if (copy_from_user(&mode, argp, sizeof(mode)))
|
||||
return -EFAULT;
|
||||
switch ((ret = parport_negotiate (port, mode))) {
|
||||
switch ((ret = parport_negotiate(port, mode))) {
|
||||
case 0: break;
|
||||
case -1: /* handshake failed, peripheral not IEEE 1284 */
|
||||
ret = -EIO;
|
||||
|
@ -572,11 +590,11 @@ static int pp_do_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
|||
ret = -ENXIO;
|
||||
break;
|
||||
}
|
||||
pp_enable_irq (pp);
|
||||
pp_enable_irq(pp);
|
||||
return ret;
|
||||
|
||||
case PPWCTLONIRQ:
|
||||
if (copy_from_user (®, argp, sizeof (reg)))
|
||||
if (copy_from_user(®, argp, sizeof(reg)))
|
||||
return -EFAULT;
|
||||
|
||||
/* Remember what to set the control lines to, for next
|
||||
|
@ -586,39 +604,50 @@ static int pp_do_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
|||
return 0;
|
||||
|
||||
case PPCLRIRQ:
|
||||
ret = atomic_read (&pp->irqc);
|
||||
if (copy_to_user (argp, &ret, sizeof (ret)))
|
||||
ret = atomic_read(&pp->irqc);
|
||||
if (copy_to_user(argp, &ret, sizeof(ret)))
|
||||
return -EFAULT;
|
||||
atomic_sub (ret, &pp->irqc);
|
||||
atomic_sub(ret, &pp->irqc);
|
||||
return 0;
|
||||
|
||||
case PPSETTIME:
|
||||
if (copy_from_user (&par_timeout, argp, sizeof(struct timeval))) {
|
||||
case PPSETTIME32:
|
||||
if (copy_from_user(time32, argp, sizeof(time32)))
|
||||
return -EFAULT;
|
||||
}
|
||||
/* Convert to jiffies, place in pp->pdev->timeout */
|
||||
if ((par_timeout.tv_sec < 0) || (par_timeout.tv_usec < 0)) {
|
||||
|
||||
return pp_set_timeout(pp->pdev, time32[0], time32[1]);
|
||||
|
||||
case PPSETTIME64:
|
||||
if (copy_from_user(time64, argp, sizeof(time64)))
|
||||
return -EFAULT;
|
||||
|
||||
return pp_set_timeout(pp->pdev, time64[0], time64[1]);
|
||||
|
||||
case PPGETTIME32:
|
||||
jiffies_to_timespec64(pp->pdev->timeout, &ts);
|
||||
time32[0] = ts.tv_sec;
|
||||
time32[1] = ts.tv_nsec / NSEC_PER_USEC;
|
||||
if ((time32[0] < 0) || (time32[1] < 0))
|
||||
return -EINVAL;
|
||||
}
|
||||
to_jiffies = ROUND_UP(par_timeout.tv_usec, 1000000/HZ);
|
||||
to_jiffies += par_timeout.tv_sec * (long)HZ;
|
||||
if (to_jiffies <= 0) {
|
||||
return -EINVAL;
|
||||
}
|
||||
pp->pdev->timeout = to_jiffies;
|
||||
|
||||
if (copy_to_user(argp, time32, sizeof(time32)))
|
||||
return -EFAULT;
|
||||
|
||||
return 0;
|
||||
|
||||
case PPGETTIME:
|
||||
to_jiffies = pp->pdev->timeout;
|
||||
memset(&par_timeout, 0, sizeof(par_timeout));
|
||||
par_timeout.tv_sec = to_jiffies / HZ;
|
||||
par_timeout.tv_usec = (to_jiffies % (long)HZ) * (1000000/HZ);
|
||||
if (copy_to_user (argp, &par_timeout, sizeof(struct timeval)))
|
||||
case PPGETTIME64:
|
||||
jiffies_to_timespec64(pp->pdev->timeout, &ts);
|
||||
time64[0] = ts.tv_sec;
|
||||
time64[1] = ts.tv_nsec / NSEC_PER_USEC;
|
||||
if ((time64[0] < 0) || (time64[1] < 0))
|
||||
return -EINVAL;
|
||||
|
||||
if (copy_to_user(argp, time64, sizeof(time64)))
|
||||
return -EFAULT;
|
||||
|
||||
return 0;
|
||||
|
||||
default:
|
||||
pr_debug(CHRDEV "%x: What? (cmd=0x%x)\n", minor, cmd);
|
||||
dev_dbg(&pp->pdev->dev, "What? (cmd=0x%x)\n", cmd);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
|
@ -629,13 +658,22 @@ static int pp_do_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
|||
static long pp_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
long ret;
|
||||
|
||||
mutex_lock(&pp_do_mutex);
|
||||
ret = pp_do_ioctl(file, cmd, arg);
|
||||
mutex_unlock(&pp_do_mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int pp_open (struct inode * inode, struct file * file)
|
||||
#ifdef CONFIG_COMPAT
|
||||
static long pp_compat_ioctl(struct file *file, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
return pp_ioctl(file, cmd, (unsigned long)compat_ptr(arg));
|
||||
}
|
||||
#endif
|
||||
|
||||
static int pp_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
unsigned int minor = iminor(inode);
|
||||
struct pp_struct *pp;
|
||||
|
@ -643,16 +681,16 @@ static int pp_open (struct inode * inode, struct file * file)
|
|||
if (minor >= PARPORT_MAX)
|
||||
return -ENXIO;
|
||||
|
||||
pp = kmalloc (sizeof (struct pp_struct), GFP_KERNEL);
|
||||
pp = kmalloc(sizeof(struct pp_struct), GFP_KERNEL);
|
||||
if (!pp)
|
||||
return -ENOMEM;
|
||||
|
||||
pp->state.mode = IEEE1284_MODE_COMPAT;
|
||||
pp->state.phase = init_phase (pp->state.mode);
|
||||
pp->state.phase = init_phase(pp->state.mode);
|
||||
pp->flags = 0;
|
||||
pp->irqresponse = 0;
|
||||
atomic_set (&pp->irqc, 0);
|
||||
init_waitqueue_head (&pp->irq_wait);
|
||||
atomic_set(&pp->irqc, 0);
|
||||
init_waitqueue_head(&pp->irq_wait);
|
||||
|
||||
/* Defer the actual device registration until the first claim.
|
||||
* That way, we know whether or not the driver wants to have
|
||||
|
@ -664,7 +702,7 @@ static int pp_open (struct inode * inode, struct file * file)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int pp_release (struct inode * inode, struct file * file)
|
||||
static int pp_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
unsigned int minor = iminor(inode);
|
||||
struct pp_struct *pp = file->private_data;
|
||||
|
@ -673,10 +711,10 @@ static int pp_release (struct inode * inode, struct file * file)
|
|||
compat_negot = 0;
|
||||
if (!(pp->flags & PP_CLAIMED) && pp->pdev &&
|
||||
(pp->state.mode != IEEE1284_MODE_COMPAT)) {
|
||||
struct ieee1284_info *info;
|
||||
struct ieee1284_info *info;
|
||||
|
||||
/* parport released, but not in compatibility mode */
|
||||
parport_claim_or_block (pp->pdev);
|
||||
parport_claim_or_block(pp->pdev);
|
||||
pp->flags |= PP_CLAIMED;
|
||||
info = &pp->pdev->port->ieee1284;
|
||||
pp->saved_state.mode = info->mode;
|
||||
|
@ -689,9 +727,9 @@ static int pp_release (struct inode * inode, struct file * file)
|
|||
compat_negot = 2;
|
||||
}
|
||||
if (compat_negot) {
|
||||
parport_negotiate (pp->pdev->port, IEEE1284_MODE_COMPAT);
|
||||
pr_debug(CHRDEV "%x: negotiated back to compatibility "
|
||||
"mode because user-space forgot\n", minor);
|
||||
parport_negotiate(pp->pdev->port, IEEE1284_MODE_COMPAT);
|
||||
dev_dbg(&pp->pdev->dev,
|
||||
"negotiated back to compatibility mode because user-space forgot\n");
|
||||
}
|
||||
|
||||
if (pp->flags & PP_CLAIMED) {
|
||||
|
@ -702,7 +740,7 @@ static int pp_release (struct inode * inode, struct file * file)
|
|||
pp->state.phase = info->phase;
|
||||
info->mode = pp->saved_state.mode;
|
||||
info->phase = pp->saved_state.phase;
|
||||
parport_release (pp->pdev);
|
||||
parport_release(pp->pdev);
|
||||
if (compat_negot != 1) {
|
||||
pr_debug(CHRDEV "%x: released pardevice "
|
||||
"because user-space forgot\n", minor);
|
||||
|
@ -711,25 +749,26 @@ static int pp_release (struct inode * inode, struct file * file)
|
|||
|
||||
if (pp->pdev) {
|
||||
const char *name = pp->pdev->name;
|
||||
parport_unregister_device (pp->pdev);
|
||||
kfree (name);
|
||||
|
||||
parport_unregister_device(pp->pdev);
|
||||
kfree(name);
|
||||
pp->pdev = NULL;
|
||||
pr_debug(CHRDEV "%x: unregistered pardevice\n", minor);
|
||||
}
|
||||
|
||||
kfree (pp);
|
||||
kfree(pp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* No kernel lock held - fine */
|
||||
static unsigned int pp_poll (struct file * file, poll_table * wait)
|
||||
static unsigned int pp_poll(struct file *file, poll_table *wait)
|
||||
{
|
||||
struct pp_struct *pp = file->private_data;
|
||||
unsigned int mask = 0;
|
||||
|
||||
poll_wait (file, &pp->irq_wait, wait);
|
||||
if (atomic_read (&pp->irqc))
|
||||
poll_wait(file, &pp->irq_wait, wait);
|
||||
if (atomic_read(&pp->irqc))
|
||||
mask |= POLLIN | POLLRDNORM;
|
||||
|
||||
return mask;
|
||||
|
@ -744,6 +783,9 @@ static const struct file_operations pp_fops = {
|
|||
.write = pp_write,
|
||||
.poll = pp_poll,
|
||||
.unlocked_ioctl = pp_ioctl,
|
||||
#ifdef CONFIG_COMPAT
|
||||
.compat_ioctl = pp_compat_ioctl,
|
||||
#endif
|
||||
.open = pp_open,
|
||||
.release = pp_release,
|
||||
};
|
||||
|
@ -759,19 +801,32 @@ static void pp_detach(struct parport *port)
|
|||
device_destroy(ppdev_class, MKDEV(PP_MAJOR, port->number));
|
||||
}
|
||||
|
||||
static int pp_probe(struct pardevice *par_dev)
|
||||
{
|
||||
struct device_driver *drv = par_dev->dev.driver;
|
||||
int len = strlen(drv->name);
|
||||
|
||||
if (strncmp(par_dev->name, drv->name, len))
|
||||
return -ENODEV;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct parport_driver pp_driver = {
|
||||
.name = CHRDEV,
|
||||
.attach = pp_attach,
|
||||
.probe = pp_probe,
|
||||
.match_port = pp_attach,
|
||||
.detach = pp_detach,
|
||||
.devmodel = true,
|
||||
};
|
||||
|
||||
static int __init ppdev_init (void)
|
||||
static int __init ppdev_init(void)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
if (register_chrdev (PP_MAJOR, CHRDEV, &pp_fops)) {
|
||||
printk (KERN_WARNING CHRDEV ": unable to get major %d\n",
|
||||
PP_MAJOR);
|
||||
if (register_chrdev(PP_MAJOR, CHRDEV, &pp_fops)) {
|
||||
printk(KERN_WARNING CHRDEV ": unable to get major %d\n",
|
||||
PP_MAJOR);
|
||||
return -EIO;
|
||||
}
|
||||
ppdev_class = class_create(THIS_MODULE, CHRDEV);
|
||||
|
@ -781,11 +836,11 @@ static int __init ppdev_init (void)
|
|||
}
|
||||
err = parport_register_driver(&pp_driver);
|
||||
if (err < 0) {
|
||||
printk (KERN_WARNING CHRDEV ": unable to register with parport\n");
|
||||
printk(KERN_WARNING CHRDEV ": unable to register with parport\n");
|
||||
goto out_class;
|
||||
}
|
||||
|
||||
printk (KERN_INFO PP_VERSION "\n");
|
||||
printk(KERN_INFO PP_VERSION "\n");
|
||||
goto out;
|
||||
|
||||
out_class:
|
||||
|
@ -796,12 +851,12 @@ out:
|
|||
return err;
|
||||
}
|
||||
|
||||
static void __exit ppdev_cleanup (void)
|
||||
static void __exit ppdev_cleanup(void)
|
||||
{
|
||||
/* Clean up all parport stuff */
|
||||
parport_unregister_driver(&pp_driver);
|
||||
class_destroy(ppdev_class);
|
||||
unregister_chrdev (PP_MAJOR, CHRDEV);
|
||||
unregister_chrdev(PP_MAJOR, CHRDEV);
|
||||
}
|
||||
|
||||
module_init(ppdev_init);
|
||||
|
|
|
@ -334,10 +334,8 @@ static int __init raw_init(void)
|
|||
|
||||
cdev_init(&raw_cdev, &raw_fops);
|
||||
ret = cdev_add(&raw_cdev, dev, max_raw_minors);
|
||||
if (ret) {
|
||||
if (ret)
|
||||
goto error_region;
|
||||
}
|
||||
|
||||
raw_class = class_create(THIS_MODULE, "raw");
|
||||
if (IS_ERR(raw_class)) {
|
||||
printk(KERN_ERR "Error creating raw class.\n");
|
||||
|
|
|
@ -509,7 +509,7 @@ static int xilly_setupchannels(struct xilly_endpoint *ep,
|
|||
channel->log2_element_size = ((format > 2) ?
|
||||
2 : format);
|
||||
|
||||
bytebufsize = channel->rd_buf_size = bufsize *
|
||||
bytebufsize = bufsize *
|
||||
(1 << channel->log2_element_size);
|
||||
|
||||
buffers = devm_kcalloc(dev, bufnum,
|
||||
|
@ -523,6 +523,7 @@ static int xilly_setupchannels(struct xilly_endpoint *ep,
|
|||
|
||||
if (!is_writebuf) {
|
||||
channel->num_rd_buffers = bufnum;
|
||||
channel->rd_buf_size = bytebufsize;
|
||||
channel->rd_allow_partial = allowpartial;
|
||||
channel->rd_synchronous = synchronous;
|
||||
channel->rd_exclusive_open = exclusive_open;
|
||||
|
@ -533,6 +534,7 @@ static int xilly_setupchannels(struct xilly_endpoint *ep,
|
|||
bufnum, bytebufsize);
|
||||
} else if (channelnum > 0) {
|
||||
channel->num_wr_buffers = bufnum;
|
||||
channel->wr_buf_size = bytebufsize;
|
||||
|
||||
channel->seekable = seekable;
|
||||
channel->wr_supports_nonempty = supports_nonempty;
|
||||
|
|
|
@ -185,7 +185,7 @@ static void arizona_extcon_hp_clamp(struct arizona_extcon_info *info,
|
|||
break;
|
||||
};
|
||||
|
||||
mutex_lock(&arizona->dapm->card->dapm_mutex);
|
||||
snd_soc_dapm_mutex_lock(arizona->dapm);
|
||||
|
||||
arizona->hpdet_clamp = clamp;
|
||||
|
||||
|
@ -227,7 +227,7 @@ static void arizona_extcon_hp_clamp(struct arizona_extcon_info *info,
|
|||
ret);
|
||||
}
|
||||
|
||||
mutex_unlock(&arizona->dapm->card->dapm_mutex);
|
||||
snd_soc_dapm_mutex_unlock(arizona->dapm);
|
||||
}
|
||||
|
||||
static void arizona_extcon_set_mode(struct arizona_extcon_info *info, int mode)
|
||||
|
|
|
@ -126,7 +126,7 @@ static int gpio_extcon_probe(struct platform_device *pdev)
|
|||
INIT_DELAYED_WORK(&data->work, gpio_extcon_work);
|
||||
|
||||
/*
|
||||
* Request the interrput of gpio to detect whether external connector
|
||||
* Request the interrupt of gpio to detect whether external connector
|
||||
* is attached or detached.
|
||||
*/
|
||||
ret = devm_request_any_context_irq(&pdev->dev, data->irq,
|
||||
|
|
|
@ -150,6 +150,7 @@ enum max14577_muic_acc_type {
|
|||
|
||||
static const unsigned int max14577_extcon_cable[] = {
|
||||
EXTCON_USB,
|
||||
EXTCON_CHG_USB_SDP,
|
||||
EXTCON_CHG_USB_DCP,
|
||||
EXTCON_CHG_USB_FAST,
|
||||
EXTCON_CHG_USB_SLOW,
|
||||
|
@ -454,6 +455,8 @@ static int max14577_muic_chg_handler(struct max14577_muic_info *info)
|
|||
return ret;
|
||||
|
||||
extcon_set_cable_state_(info->edev, EXTCON_USB, attached);
|
||||
extcon_set_cable_state_(info->edev, EXTCON_CHG_USB_SDP,
|
||||
attached);
|
||||
break;
|
||||
case MAX14577_CHARGER_TYPE_DEDICATED_CHG:
|
||||
extcon_set_cable_state_(info->edev, EXTCON_CHG_USB_DCP,
|
||||
|
|
|
@ -204,6 +204,7 @@ enum max77693_muic_acc_type {
|
|||
static const unsigned int max77693_extcon_cable[] = {
|
||||
EXTCON_USB,
|
||||
EXTCON_USB_HOST,
|
||||
EXTCON_CHG_USB_SDP,
|
||||
EXTCON_CHG_USB_DCP,
|
||||
EXTCON_CHG_USB_FAST,
|
||||
EXTCON_CHG_USB_SLOW,
|
||||
|
@ -512,8 +513,11 @@ static int max77693_muic_dock_handler(struct max77693_muic_info *info,
|
|||
break;
|
||||
case MAX77693_MUIC_ADC_AV_CABLE_NOLOAD: /* Dock-Audio */
|
||||
dock_id = EXTCON_DOCK;
|
||||
if (!attached)
|
||||
if (!attached) {
|
||||
extcon_set_cable_state_(info->edev, EXTCON_USB, false);
|
||||
extcon_set_cable_state_(info->edev, EXTCON_CHG_USB_SDP,
|
||||
false);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
dev_err(info->dev, "failed to detect %s dock device\n",
|
||||
|
@ -601,6 +605,8 @@ static int max77693_muic_adc_ground_handler(struct max77693_muic_info *info)
|
|||
if (ret < 0)
|
||||
return ret;
|
||||
extcon_set_cable_state_(info->edev, EXTCON_USB, attached);
|
||||
extcon_set_cable_state_(info->edev, EXTCON_CHG_USB_SDP,
|
||||
attached);
|
||||
break;
|
||||
case MAX77693_MUIC_GND_MHL:
|
||||
case MAX77693_MUIC_GND_MHL_VB:
|
||||
|
@ -830,6 +836,8 @@ static int max77693_muic_chg_handler(struct max77693_muic_info *info)
|
|||
*/
|
||||
extcon_set_cable_state_(info->edev, EXTCON_USB,
|
||||
attached);
|
||||
extcon_set_cable_state_(info->edev, EXTCON_CHG_USB_SDP,
|
||||
attached);
|
||||
|
||||
if (!cable_attached)
|
||||
extcon_set_cable_state_(info->edev, EXTCON_DOCK,
|
||||
|
@ -899,6 +907,8 @@ static int max77693_muic_chg_handler(struct max77693_muic_info *info)
|
|||
|
||||
extcon_set_cable_state_(info->edev, EXTCON_USB,
|
||||
attached);
|
||||
extcon_set_cable_state_(info->edev, EXTCON_CHG_USB_SDP,
|
||||
attached);
|
||||
break;
|
||||
case MAX77693_CHARGER_TYPE_DEDICATED_CHG:
|
||||
/* Only TA cable */
|
||||
|
|
|
@ -122,6 +122,7 @@ enum max77843_muic_charger_type {
|
|||
static const unsigned int max77843_extcon_cable[] = {
|
||||
EXTCON_USB,
|
||||
EXTCON_USB_HOST,
|
||||
EXTCON_CHG_USB_SDP,
|
||||
EXTCON_CHG_USB_DCP,
|
||||
EXTCON_CHG_USB_CDP,
|
||||
EXTCON_CHG_USB_FAST,
|
||||
|
@ -486,6 +487,8 @@ static int max77843_muic_chg_handler(struct max77843_muic_info *info)
|
|||
return ret;
|
||||
|
||||
extcon_set_cable_state_(info->edev, EXTCON_USB, attached);
|
||||
extcon_set_cable_state_(info->edev, EXTCON_CHG_USB_SDP,
|
||||
attached);
|
||||
break;
|
||||
case MAX77843_MUIC_CHG_DOWNSTREAM:
|
||||
ret = max77843_muic_set_path(info,
|
||||
|
@ -803,7 +806,7 @@ static int max77843_muic_probe(struct platform_device *pdev)
|
|||
/* Clear IRQ bits before request IRQs */
|
||||
ret = regmap_bulk_read(max77843->regmap_muic,
|
||||
MAX77843_MUIC_REG_INT1, info->status,
|
||||
MAX77843_MUIC_IRQ_NUM);
|
||||
MAX77843_MUIC_STATUS_NUM);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Failed to Clear IRQ bits\n");
|
||||
goto err_muic_irq;
|
||||
|
|
|
@ -148,6 +148,7 @@ struct max8997_muic_info {
|
|||
static const unsigned int max8997_extcon_cable[] = {
|
||||
EXTCON_USB,
|
||||
EXTCON_USB_HOST,
|
||||
EXTCON_CHG_USB_SDP,
|
||||
EXTCON_CHG_USB_DCP,
|
||||
EXTCON_CHG_USB_FAST,
|
||||
EXTCON_CHG_USB_SLOW,
|
||||
|
@ -334,6 +335,8 @@ static int max8997_muic_handle_usb(struct max8997_muic_info *info,
|
|||
break;
|
||||
case MAX8997_USB_DEVICE:
|
||||
extcon_set_cable_state_(info->edev, EXTCON_USB, attached);
|
||||
extcon_set_cable_state_(info->edev, EXTCON_CHG_USB_SDP,
|
||||
attached);
|
||||
break;
|
||||
default:
|
||||
dev_err(info->dev, "failed to detect %s usb cable\n",
|
||||
|
|
|
@ -216,11 +216,23 @@ static int palmas_usb_probe(struct platform_device *pdev)
|
|||
return PTR_ERR(palmas_usb->id_gpiod);
|
||||
}
|
||||
|
||||
palmas_usb->vbus_gpiod = devm_gpiod_get_optional(&pdev->dev, "vbus",
|
||||
GPIOD_IN);
|
||||
if (IS_ERR(palmas_usb->vbus_gpiod)) {
|
||||
dev_err(&pdev->dev, "failed to get vbus gpio\n");
|
||||
return PTR_ERR(palmas_usb->vbus_gpiod);
|
||||
}
|
||||
|
||||
if (palmas_usb->enable_id_detection && palmas_usb->id_gpiod) {
|
||||
palmas_usb->enable_id_detection = false;
|
||||
palmas_usb->enable_gpio_id_detection = true;
|
||||
}
|
||||
|
||||
if (palmas_usb->enable_vbus_detection && palmas_usb->vbus_gpiod) {
|
||||
palmas_usb->enable_vbus_detection = false;
|
||||
palmas_usb->enable_gpio_vbus_detection = true;
|
||||
}
|
||||
|
||||
if (palmas_usb->enable_gpio_id_detection) {
|
||||
u32 debounce;
|
||||
|
||||
|
@ -266,7 +278,7 @@ static int palmas_usb_probe(struct platform_device *pdev)
|
|||
palmas_usb->id_irq,
|
||||
NULL, palmas_id_irq_handler,
|
||||
IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING |
|
||||
IRQF_ONESHOT | IRQF_EARLY_RESUME,
|
||||
IRQF_ONESHOT,
|
||||
"palmas_usb_id", palmas_usb);
|
||||
if (status < 0) {
|
||||
dev_err(&pdev->dev, "can't get IRQ %d, err %d\n",
|
||||
|
@ -304,13 +316,47 @@ static int palmas_usb_probe(struct platform_device *pdev)
|
|||
palmas_usb->vbus_irq, NULL,
|
||||
palmas_vbus_irq_handler,
|
||||
IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING |
|
||||
IRQF_ONESHOT | IRQF_EARLY_RESUME,
|
||||
IRQF_ONESHOT,
|
||||
"palmas_usb_vbus", palmas_usb);
|
||||
if (status < 0) {
|
||||
dev_err(&pdev->dev, "can't get IRQ %d, err %d\n",
|
||||
palmas_usb->vbus_irq, status);
|
||||
return status;
|
||||
}
|
||||
} else if (palmas_usb->enable_gpio_vbus_detection) {
|
||||
/* remux GPIO_1 as VBUSDET */
|
||||
status = palmas_update_bits(palmas,
|
||||
PALMAS_PU_PD_OD_BASE,
|
||||
PALMAS_PRIMARY_SECONDARY_PAD1,
|
||||
PALMAS_PRIMARY_SECONDARY_PAD1_GPIO_1_MASK,
|
||||
(1 << PALMAS_PRIMARY_SECONDARY_PAD1_GPIO_1_SHIFT));
|
||||
if (status < 0) {
|
||||
dev_err(&pdev->dev, "can't remux GPIO1\n");
|
||||
return status;
|
||||
}
|
||||
|
||||
palmas_usb->vbus_otg_irq = regmap_irq_get_virq(palmas->irq_data,
|
||||
PALMAS_VBUS_OTG_IRQ);
|
||||
palmas_usb->gpio_vbus_irq = gpiod_to_irq(palmas_usb->vbus_gpiod);
|
||||
if (palmas_usb->gpio_vbus_irq < 0) {
|
||||
dev_err(&pdev->dev, "failed to get vbus irq\n");
|
||||
return palmas_usb->gpio_vbus_irq;
|
||||
}
|
||||
status = devm_request_threaded_irq(&pdev->dev,
|
||||
palmas_usb->gpio_vbus_irq,
|
||||
NULL,
|
||||
palmas_vbus_irq_handler,
|
||||
IRQF_TRIGGER_FALLING |
|
||||
IRQF_TRIGGER_RISING |
|
||||
IRQF_ONESHOT |
|
||||
IRQF_EARLY_RESUME,
|
||||
"palmas_usb_vbus",
|
||||
palmas_usb);
|
||||
if (status < 0) {
|
||||
dev_err(&pdev->dev,
|
||||
"failed to request handler for vbus irq\n");
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
palmas_enable_irq(palmas_usb);
|
||||
|
@ -337,6 +383,8 @@ static int palmas_usb_suspend(struct device *dev)
|
|||
if (device_may_wakeup(dev)) {
|
||||
if (palmas_usb->enable_vbus_detection)
|
||||
enable_irq_wake(palmas_usb->vbus_irq);
|
||||
if (palmas_usb->enable_gpio_vbus_detection)
|
||||
enable_irq_wake(palmas_usb->gpio_vbus_irq);
|
||||
if (palmas_usb->enable_id_detection)
|
||||
enable_irq_wake(palmas_usb->id_irq);
|
||||
if (palmas_usb->enable_gpio_id_detection)
|
||||
|
@ -352,6 +400,8 @@ static int palmas_usb_resume(struct device *dev)
|
|||
if (device_may_wakeup(dev)) {
|
||||
if (palmas_usb->enable_vbus_detection)
|
||||
disable_irq_wake(palmas_usb->vbus_irq);
|
||||
if (palmas_usb->enable_gpio_vbus_detection)
|
||||
disable_irq_wake(palmas_usb->gpio_vbus_irq);
|
||||
if (palmas_usb->enable_id_detection)
|
||||
disable_irq_wake(palmas_usb->id_irq);
|
||||
if (palmas_usb->enable_gpio_id_detection)
|
||||
|
|
|
@ -93,6 +93,7 @@ static struct reg_data rt8973a_reg_data[] = {
|
|||
static const unsigned int rt8973a_extcon_cable[] = {
|
||||
EXTCON_USB,
|
||||
EXTCON_USB_HOST,
|
||||
EXTCON_CHG_USB_SDP,
|
||||
EXTCON_CHG_USB_DCP,
|
||||
EXTCON_JIG,
|
||||
EXTCON_NONE,
|
||||
|
@ -398,6 +399,9 @@ static int rt8973a_muic_cable_handler(struct rt8973a_muic_info *info,
|
|||
|
||||
/* Change the state of external accessory */
|
||||
extcon_set_cable_state_(info->edev, id, attached);
|
||||
if (id == EXTCON_USB)
|
||||
extcon_set_cable_state_(info->edev, EXTCON_CHG_USB_SDP,
|
||||
attached);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -663,7 +667,7 @@ MODULE_DEVICE_TABLE(of, rt8973a_dt_match);
|
|||
#ifdef CONFIG_PM_SLEEP
|
||||
static int rt8973a_muic_suspend(struct device *dev)
|
||||
{
|
||||
struct i2c_client *i2c = container_of(dev, struct i2c_client, dev);
|
||||
struct i2c_client *i2c = to_i2c_client(dev);
|
||||
struct rt8973a_muic_info *info = i2c_get_clientdata(i2c);
|
||||
|
||||
enable_irq_wake(info->irq);
|
||||
|
@ -673,7 +677,7 @@ static int rt8973a_muic_suspend(struct device *dev)
|
|||
|
||||
static int rt8973a_muic_resume(struct device *dev)
|
||||
{
|
||||
struct i2c_client *i2c = container_of(dev, struct i2c_client, dev);
|
||||
struct i2c_client *i2c = to_i2c_client(dev);
|
||||
struct rt8973a_muic_info *info = i2c_get_clientdata(i2c);
|
||||
|
||||
disable_irq_wake(info->irq);
|
||||
|
|
|
@ -95,6 +95,7 @@ static struct reg_data sm5502_reg_data[] = {
|
|||
static const unsigned int sm5502_extcon_cable[] = {
|
||||
EXTCON_USB,
|
||||
EXTCON_USB_HOST,
|
||||
EXTCON_CHG_USB_SDP,
|
||||
EXTCON_CHG_USB_DCP,
|
||||
EXTCON_NONE,
|
||||
};
|
||||
|
@ -411,6 +412,9 @@ static int sm5502_muic_cable_handler(struct sm5502_muic_info *info,
|
|||
|
||||
/* Change the state of external accessory */
|
||||
extcon_set_cable_state_(info->edev, id, attached);
|
||||
if (id == EXTCON_USB)
|
||||
extcon_set_cable_state_(info->edev, EXTCON_CHG_USB_SDP,
|
||||
attached);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -655,7 +659,7 @@ MODULE_DEVICE_TABLE(of, sm5502_dt_match);
|
|||
#ifdef CONFIG_PM_SLEEP
|
||||
static int sm5502_muic_suspend(struct device *dev)
|
||||
{
|
||||
struct i2c_client *i2c = container_of(dev, struct i2c_client, dev);
|
||||
struct i2c_client *i2c = to_i2c_client(dev);
|
||||
struct sm5502_muic_info *info = i2c_get_clientdata(i2c);
|
||||
|
||||
enable_irq_wake(info->irq);
|
||||
|
@ -665,7 +669,7 @@ static int sm5502_muic_suspend(struct device *dev)
|
|||
|
||||
static int sm5502_muic_resume(struct device *dev)
|
||||
{
|
||||
struct i2c_client *i2c = container_of(dev, struct i2c_client, dev);
|
||||
struct i2c_client *i2c = to_i2c_client(dev);
|
||||
struct sm5502_muic_info *info = i2c_get_clientdata(i2c);
|
||||
|
||||
disable_irq_wake(info->irq);
|
||||
|
|
|
@ -219,6 +219,21 @@ error0:
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(vmbus_open);
|
||||
|
||||
/* Used for Hyper-V Socket: a guest client's connect() to the host */
|
||||
int vmbus_send_tl_connect_request(const uuid_le *shv_guest_servie_id,
|
||||
const uuid_le *shv_host_servie_id)
|
||||
{
|
||||
struct vmbus_channel_tl_connect_request conn_msg;
|
||||
|
||||
memset(&conn_msg, 0, sizeof(conn_msg));
|
||||
conn_msg.header.msgtype = CHANNELMSG_TL_CONNECT_REQUEST;
|
||||
conn_msg.guest_endpoint_id = *shv_guest_servie_id;
|
||||
conn_msg.host_service_id = *shv_host_servie_id;
|
||||
|
||||
return vmbus_post_msg(&conn_msg, sizeof(conn_msg));
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(vmbus_send_tl_connect_request);
|
||||
|
||||
/*
|
||||
* create_gpadl_header - Creates a gpadl for the specified buffer
|
||||
*/
|
||||
|
@ -624,6 +639,7 @@ int vmbus_sendpacket_ctl(struct vmbus_channel *channel, void *buffer,
|
|||
u64 aligned_data = 0;
|
||||
int ret;
|
||||
bool signal = false;
|
||||
bool lock = channel->acquire_ring_lock;
|
||||
int num_vecs = ((bufferlen != 0) ? 3 : 1);
|
||||
|
||||
|
||||
|
@ -643,7 +659,7 @@ int vmbus_sendpacket_ctl(struct vmbus_channel *channel, void *buffer,
|
|||
bufferlist[2].iov_len = (packetlen_aligned - packetlen);
|
||||
|
||||
ret = hv_ringbuffer_write(&channel->outbound, bufferlist, num_vecs,
|
||||
&signal);
|
||||
&signal, lock);
|
||||
|
||||
/*
|
||||
* Signalling the host is conditional on many factors:
|
||||
|
@ -659,6 +675,9 @@ int vmbus_sendpacket_ctl(struct vmbus_channel *channel, void *buffer,
|
|||
* If we cannot write to the ring-buffer; signal the host
|
||||
* even if we may not have written anything. This is a rare
|
||||
* enough condition that it should not matter.
|
||||
* NOTE: in this case, the hvsock channel is an exception, because
|
||||
* it looks the host side's hvsock implementation has a throttling
|
||||
* mechanism which can hurt the performance otherwise.
|
||||
*/
|
||||
|
||||
if (channel->signal_policy)
|
||||
|
@ -666,7 +685,8 @@ int vmbus_sendpacket_ctl(struct vmbus_channel *channel, void *buffer,
|
|||
else
|
||||
kick_q = true;
|
||||
|
||||
if (((ret == 0) && kick_q && signal) || (ret))
|
||||
if (((ret == 0) && kick_q && signal) ||
|
||||
(ret && !is_hvsock_channel(channel)))
|
||||
vmbus_setevent(channel);
|
||||
|
||||
return ret;
|
||||
|
@ -719,6 +739,7 @@ int vmbus_sendpacket_pagebuffer_ctl(struct vmbus_channel *channel,
|
|||
struct kvec bufferlist[3];
|
||||
u64 aligned_data = 0;
|
||||
bool signal = false;
|
||||
bool lock = channel->acquire_ring_lock;
|
||||
|
||||
if (pagecount > MAX_PAGE_BUFFER_COUNT)
|
||||
return -EINVAL;
|
||||
|
@ -755,7 +776,8 @@ int vmbus_sendpacket_pagebuffer_ctl(struct vmbus_channel *channel,
|
|||
bufferlist[2].iov_base = &aligned_data;
|
||||
bufferlist[2].iov_len = (packetlen_aligned - packetlen);
|
||||
|
||||
ret = hv_ringbuffer_write(&channel->outbound, bufferlist, 3, &signal);
|
||||
ret = hv_ringbuffer_write(&channel->outbound, bufferlist, 3,
|
||||
&signal, lock);
|
||||
|
||||
/*
|
||||
* Signalling the host is conditional on many factors:
|
||||
|
@ -818,6 +840,7 @@ int vmbus_sendpacket_mpb_desc(struct vmbus_channel *channel,
|
|||
struct kvec bufferlist[3];
|
||||
u64 aligned_data = 0;
|
||||
bool signal = false;
|
||||
bool lock = channel->acquire_ring_lock;
|
||||
|
||||
packetlen = desc_size + bufferlen;
|
||||
packetlen_aligned = ALIGN(packetlen, sizeof(u64));
|
||||
|
@ -837,7 +860,8 @@ int vmbus_sendpacket_mpb_desc(struct vmbus_channel *channel,
|
|||
bufferlist[2].iov_base = &aligned_data;
|
||||
bufferlist[2].iov_len = (packetlen_aligned - packetlen);
|
||||
|
||||
ret = hv_ringbuffer_write(&channel->outbound, bufferlist, 3, &signal);
|
||||
ret = hv_ringbuffer_write(&channel->outbound, bufferlist, 3,
|
||||
&signal, lock);
|
||||
|
||||
if (ret == 0 && signal)
|
||||
vmbus_setevent(channel);
|
||||
|
@ -862,6 +886,7 @@ int vmbus_sendpacket_multipagebuffer(struct vmbus_channel *channel,
|
|||
struct kvec bufferlist[3];
|
||||
u64 aligned_data = 0;
|
||||
bool signal = false;
|
||||
bool lock = channel->acquire_ring_lock;
|
||||
u32 pfncount = NUM_PAGES_SPANNED(multi_pagebuffer->offset,
|
||||
multi_pagebuffer->len);
|
||||
|
||||
|
@ -900,7 +925,8 @@ int vmbus_sendpacket_multipagebuffer(struct vmbus_channel *channel,
|
|||
bufferlist[2].iov_base = &aligned_data;
|
||||
bufferlist[2].iov_len = (packetlen_aligned - packetlen);
|
||||
|
||||
ret = hv_ringbuffer_write(&channel->outbound, bufferlist, 3, &signal);
|
||||
ret = hv_ringbuffer_write(&channel->outbound, bufferlist, 3,
|
||||
&signal, lock);
|
||||
|
||||
if (ret == 0 && signal)
|
||||
vmbus_setevent(channel);
|
||||
|
|
|
@ -28,12 +28,127 @@
|
|||
#include <linux/list.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/completion.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/hyperv.h>
|
||||
|
||||
#include "hyperv_vmbus.h"
|
||||
|
||||
static void init_vp_index(struct vmbus_channel *channel,
|
||||
const uuid_le *type_guid);
|
||||
static void init_vp_index(struct vmbus_channel *channel, u16 dev_type);
|
||||
|
||||
static const struct vmbus_device vmbus_devs[] = {
|
||||
/* IDE */
|
||||
{ .dev_type = HV_IDE,
|
||||
HV_IDE_GUID,
|
||||
.perf_device = true,
|
||||
},
|
||||
|
||||
/* SCSI */
|
||||
{ .dev_type = HV_SCSI,
|
||||
HV_SCSI_GUID,
|
||||
.perf_device = true,
|
||||
},
|
||||
|
||||
/* Fibre Channel */
|
||||
{ .dev_type = HV_FC,
|
||||
HV_SYNTHFC_GUID,
|
||||
.perf_device = true,
|
||||
},
|
||||
|
||||
/* Synthetic NIC */
|
||||
{ .dev_type = HV_NIC,
|
||||
HV_NIC_GUID,
|
||||
.perf_device = true,
|
||||
},
|
||||
|
||||
/* Network Direct */
|
||||
{ .dev_type = HV_ND,
|
||||
HV_ND_GUID,
|
||||
.perf_device = true,
|
||||
},
|
||||
|
||||
/* PCIE */
|
||||
{ .dev_type = HV_PCIE,
|
||||
HV_PCIE_GUID,
|
||||
.perf_device = true,
|
||||
},
|
||||
|
||||
/* Synthetic Frame Buffer */
|
||||
{ .dev_type = HV_FB,
|
||||
HV_SYNTHVID_GUID,
|
||||
.perf_device = false,
|
||||
},
|
||||
|
||||
/* Synthetic Keyboard */
|
||||
{ .dev_type = HV_KBD,
|
||||
HV_KBD_GUID,
|
||||
.perf_device = false,
|
||||
},
|
||||
|
||||
/* Synthetic MOUSE */
|
||||
{ .dev_type = HV_MOUSE,
|
||||
HV_MOUSE_GUID,
|
||||
.perf_device = false,
|
||||
},
|
||||
|
||||
/* KVP */
|
||||
{ .dev_type = HV_KVP,
|
||||
HV_KVP_GUID,
|
||||
.perf_device = false,
|
||||
},
|
||||
|
||||
/* Time Synch */
|
||||
{ .dev_type = HV_TS,
|
||||
HV_TS_GUID,
|
||||
.perf_device = false,
|
||||
},
|
||||
|
||||
/* Heartbeat */
|
||||
{ .dev_type = HV_HB,
|
||||
HV_HEART_BEAT_GUID,
|
||||
.perf_device = false,
|
||||
},
|
||||
|
||||
/* Shutdown */
|
||||
{ .dev_type = HV_SHUTDOWN,
|
||||
HV_SHUTDOWN_GUID,
|
||||
.perf_device = false,
|
||||
},
|
||||
|
||||
/* File copy */
|
||||
{ .dev_type = HV_FCOPY,
|
||||
HV_FCOPY_GUID,
|
||||
.perf_device = false,
|
||||
},
|
||||
|
||||
/* Backup */
|
||||
{ .dev_type = HV_BACKUP,
|
||||
HV_VSS_GUID,
|
||||
.perf_device = false,
|
||||
},
|
||||
|
||||
/* Dynamic Memory */
|
||||
{ .dev_type = HV_DM,
|
||||
HV_DM_GUID,
|
||||
.perf_device = false,
|
||||
},
|
||||
|
||||
/* Unknown GUID */
|
||||
{ .dev_type = HV_UNKOWN,
|
||||
.perf_device = false,
|
||||
},
|
||||
};
|
||||
|
||||
static u16 hv_get_dev_type(const uuid_le *guid)
|
||||
{
|
||||
u16 i;
|
||||
|
||||
for (i = HV_IDE; i < HV_UNKOWN; i++) {
|
||||
if (!uuid_le_cmp(*guid, vmbus_devs[i].guid))
|
||||
return i;
|
||||
}
|
||||
pr_info("Unknown GUID: %pUl\n", guid);
|
||||
return i;
|
||||
}
|
||||
|
||||
/**
|
||||
* vmbus_prep_negotiate_resp() - Create default response for Hyper-V Negotiate message
|
||||
|
@ -144,6 +259,7 @@ static struct vmbus_channel *alloc_channel(void)
|
|||
return NULL;
|
||||
|
||||
channel->id = atomic_inc_return(&chan_num);
|
||||
channel->acquire_ring_lock = true;
|
||||
spin_lock_init(&channel->inbound_lock);
|
||||
spin_lock_init(&channel->lock);
|
||||
|
||||
|
@ -195,6 +311,7 @@ void hv_process_channel_removal(struct vmbus_channel *channel, u32 relid)
|
|||
vmbus_release_relid(relid);
|
||||
|
||||
BUG_ON(!channel->rescind);
|
||||
BUG_ON(!mutex_is_locked(&vmbus_connection.channel_mutex));
|
||||
|
||||
if (channel->target_cpu != get_cpu()) {
|
||||
put_cpu();
|
||||
|
@ -206,9 +323,7 @@ void hv_process_channel_removal(struct vmbus_channel *channel, u32 relid)
|
|||
}
|
||||
|
||||
if (channel->primary_channel == NULL) {
|
||||
mutex_lock(&vmbus_connection.channel_mutex);
|
||||
list_del(&channel->listentry);
|
||||
mutex_unlock(&vmbus_connection.channel_mutex);
|
||||
|
||||
primary_channel = channel;
|
||||
} else {
|
||||
|
@ -251,6 +366,8 @@ static void vmbus_process_offer(struct vmbus_channel *newchannel)
|
|||
struct vmbus_channel *channel;
|
||||
bool fnew = true;
|
||||
unsigned long flags;
|
||||
u16 dev_type;
|
||||
int ret;
|
||||
|
||||
/* Make sure this is a new offer */
|
||||
mutex_lock(&vmbus_connection.channel_mutex);
|
||||
|
@ -288,7 +405,9 @@ static void vmbus_process_offer(struct vmbus_channel *newchannel)
|
|||
goto err_free_chan;
|
||||
}
|
||||
|
||||
init_vp_index(newchannel, &newchannel->offermsg.offer.if_type);
|
||||
dev_type = hv_get_dev_type(&newchannel->offermsg.offer.if_type);
|
||||
|
||||
init_vp_index(newchannel, dev_type);
|
||||
|
||||
if (newchannel->target_cpu != get_cpu()) {
|
||||
put_cpu();
|
||||
|
@ -325,12 +444,17 @@ static void vmbus_process_offer(struct vmbus_channel *newchannel)
|
|||
if (!newchannel->device_obj)
|
||||
goto err_deq_chan;
|
||||
|
||||
newchannel->device_obj->device_id = dev_type;
|
||||
/*
|
||||
* Add the new device to the bus. This will kick off device-driver
|
||||
* binding which eventually invokes the device driver's AddDevice()
|
||||
* method.
|
||||
*/
|
||||
if (vmbus_device_register(newchannel->device_obj) != 0) {
|
||||
mutex_lock(&vmbus_connection.channel_mutex);
|
||||
ret = vmbus_device_register(newchannel->device_obj);
|
||||
mutex_unlock(&vmbus_connection.channel_mutex);
|
||||
|
||||
if (ret != 0) {
|
||||
pr_err("unable to add child device object (relid %d)\n",
|
||||
newchannel->offermsg.child_relid);
|
||||
kfree(newchannel->device_obj);
|
||||
|
@ -358,37 +482,6 @@ err_free_chan:
|
|||
free_channel(newchannel);
|
||||
}
|
||||
|
||||
enum {
|
||||
IDE = 0,
|
||||
SCSI,
|
||||
FC,
|
||||
NIC,
|
||||
ND_NIC,
|
||||
PCIE,
|
||||
MAX_PERF_CHN,
|
||||
};
|
||||
|
||||
/*
|
||||
* This is an array of device_ids (device types) that are performance critical.
|
||||
* We attempt to distribute the interrupt load for these devices across
|
||||
* all available CPUs.
|
||||
*/
|
||||
static const struct hv_vmbus_device_id hp_devs[] = {
|
||||
/* IDE */
|
||||
{ HV_IDE_GUID, },
|
||||
/* Storage - SCSI */
|
||||
{ HV_SCSI_GUID, },
|
||||
/* Storage - FC */
|
||||
{ HV_SYNTHFC_GUID, },
|
||||
/* Network */
|
||||
{ HV_NIC_GUID, },
|
||||
/* NetworkDirect Guest RDMA */
|
||||
{ HV_ND_GUID, },
|
||||
/* PCI Express Pass Through */
|
||||
{ HV_PCIE_GUID, },
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* We use this state to statically distribute the channel interrupt load.
|
||||
*/
|
||||
|
@ -405,22 +498,15 @@ static int next_numa_node_id;
|
|||
* For pre-win8 hosts or non-performance critical channels we assign the
|
||||
* first CPU in the first NUMA node.
|
||||
*/
|
||||
static void init_vp_index(struct vmbus_channel *channel, const uuid_le *type_guid)
|
||||
static void init_vp_index(struct vmbus_channel *channel, u16 dev_type)
|
||||
{
|
||||
u32 cur_cpu;
|
||||
int i;
|
||||
bool perf_chn = false;
|
||||
bool perf_chn = vmbus_devs[dev_type].perf_device;
|
||||
struct vmbus_channel *primary = channel->primary_channel;
|
||||
int next_node;
|
||||
struct cpumask available_mask;
|
||||
struct cpumask *alloced_mask;
|
||||
|
||||
for (i = IDE; i < MAX_PERF_CHN; i++) {
|
||||
if (!uuid_le_cmp(*type_guid, hp_devs[i].guid)) {
|
||||
perf_chn = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ((vmbus_proto_version == VERSION_WS2008) ||
|
||||
(vmbus_proto_version == VERSION_WIN7) || (!perf_chn)) {
|
||||
/*
|
||||
|
@ -469,6 +555,17 @@ static void init_vp_index(struct vmbus_channel *channel, const uuid_le *type_gui
|
|||
cpumask_of_node(primary->numa_node));
|
||||
|
||||
cur_cpu = -1;
|
||||
|
||||
/*
|
||||
* Normally Hyper-V host doesn't create more subchannels than there
|
||||
* are VCPUs on the node but it is possible when not all present VCPUs
|
||||
* on the node are initialized by guest. Clear the alloced_cpus_in_node
|
||||
* to start over.
|
||||
*/
|
||||
if (cpumask_equal(&primary->alloced_cpus_in_node,
|
||||
cpumask_of_node(primary->numa_node)))
|
||||
cpumask_clear(&primary->alloced_cpus_in_node);
|
||||
|
||||
while (true) {
|
||||
cur_cpu = cpumask_next(cur_cpu, &available_mask);
|
||||
if (cur_cpu >= nr_cpu_ids) {
|
||||
|
@ -498,6 +595,32 @@ static void init_vp_index(struct vmbus_channel *channel, const uuid_le *type_gui
|
|||
channel->target_vp = hv_context.vp_index[cur_cpu];
|
||||
}
|
||||
|
||||
static void vmbus_wait_for_unload(void)
|
||||
{
|
||||
int cpu = smp_processor_id();
|
||||
void *page_addr = hv_context.synic_message_page[cpu];
|
||||
struct hv_message *msg = (struct hv_message *)page_addr +
|
||||
VMBUS_MESSAGE_SINT;
|
||||
struct vmbus_channel_message_header *hdr;
|
||||
bool unloaded = false;
|
||||
|
||||
while (1) {
|
||||
if (READ_ONCE(msg->header.message_type) == HVMSG_NONE) {
|
||||
mdelay(10);
|
||||
continue;
|
||||
}
|
||||
|
||||
hdr = (struct vmbus_channel_message_header *)msg->u.payload;
|
||||
if (hdr->msgtype == CHANNELMSG_UNLOAD_RESPONSE)
|
||||
unloaded = true;
|
||||
|
||||
vmbus_signal_eom(msg);
|
||||
|
||||
if (unloaded)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* vmbus_unload_response - Handler for the unload response.
|
||||
*/
|
||||
|
@ -510,7 +633,7 @@ static void vmbus_unload_response(struct vmbus_channel_message_header *hdr)
|
|||
complete(&vmbus_connection.unload_event);
|
||||
}
|
||||
|
||||
void vmbus_initiate_unload(void)
|
||||
void vmbus_initiate_unload(bool crash)
|
||||
{
|
||||
struct vmbus_channel_message_header hdr;
|
||||
|
||||
|
@ -523,7 +646,14 @@ void vmbus_initiate_unload(void)
|
|||
hdr.msgtype = CHANNELMSG_UNLOAD;
|
||||
vmbus_post_msg(&hdr, sizeof(struct vmbus_channel_message_header));
|
||||
|
||||
wait_for_completion(&vmbus_connection.unload_event);
|
||||
/*
|
||||
* vmbus_initiate_unload() is also called on crash and the crash can be
|
||||
* happening in an interrupt context, where scheduling is impossible.
|
||||
*/
|
||||
if (!crash)
|
||||
wait_for_completion(&vmbus_connection.unload_event);
|
||||
else
|
||||
vmbus_wait_for_unload();
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -592,6 +722,8 @@ static void vmbus_onoffer_rescind(struct vmbus_channel_message_header *hdr)
|
|||
struct device *dev;
|
||||
|
||||
rescind = (struct vmbus_channel_rescind_offer *)hdr;
|
||||
|
||||
mutex_lock(&vmbus_connection.channel_mutex);
|
||||
channel = relid2channel(rescind->child_relid);
|
||||
|
||||
if (channel == NULL) {
|
||||
|
@ -600,7 +732,7 @@ static void vmbus_onoffer_rescind(struct vmbus_channel_message_header *hdr)
|
|||
* vmbus_process_offer(), we have already invoked
|
||||
* vmbus_release_relid() on error.
|
||||
*/
|
||||
return;
|
||||
goto out;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&channel->lock, flags);
|
||||
|
@ -608,6 +740,10 @@ static void vmbus_onoffer_rescind(struct vmbus_channel_message_header *hdr)
|
|||
spin_unlock_irqrestore(&channel->lock, flags);
|
||||
|
||||
if (channel->device_obj) {
|
||||
if (channel->chn_rescind_callback) {
|
||||
channel->chn_rescind_callback(channel);
|
||||
goto out;
|
||||
}
|
||||
/*
|
||||
* We will have to unregister this device from the
|
||||
* driver core.
|
||||
|
@ -621,8 +757,25 @@ static void vmbus_onoffer_rescind(struct vmbus_channel_message_header *hdr)
|
|||
hv_process_channel_removal(channel,
|
||||
channel->offermsg.child_relid);
|
||||
}
|
||||
|
||||
out:
|
||||
mutex_unlock(&vmbus_connection.channel_mutex);
|
||||
}
|
||||
|
||||
void vmbus_hvsock_device_unregister(struct vmbus_channel *channel)
|
||||
{
|
||||
mutex_lock(&vmbus_connection.channel_mutex);
|
||||
|
||||
BUG_ON(!is_hvsock_channel(channel));
|
||||
|
||||
channel->rescind = true;
|
||||
vmbus_device_unregister(channel->device_obj);
|
||||
|
||||
mutex_unlock(&vmbus_connection.channel_mutex);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(vmbus_hvsock_device_unregister);
|
||||
|
||||
|
||||
/*
|
||||
* vmbus_onoffers_delivered -
|
||||
* This is invoked when all offers have been delivered.
|
||||
|
@ -825,6 +978,10 @@ struct vmbus_channel_message_table_entry
|
|||
{CHANNELMSG_VERSION_RESPONSE, 1, vmbus_onversion_response},
|
||||
{CHANNELMSG_UNLOAD, 0, NULL},
|
||||
{CHANNELMSG_UNLOAD_RESPONSE, 1, vmbus_unload_response},
|
||||
{CHANNELMSG_18, 0, NULL},
|
||||
{CHANNELMSG_19, 0, NULL},
|
||||
{CHANNELMSG_20, 0, NULL},
|
||||
{CHANNELMSG_TL_CONNECT_REQUEST, 0, NULL},
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -973,3 +1130,10 @@ bool vmbus_are_subchannels_present(struct vmbus_channel *primary)
|
|||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(vmbus_are_subchannels_present);
|
||||
|
||||
void vmbus_set_chn_rescind_callback(struct vmbus_channel *channel,
|
||||
void (*chn_rescind_cb)(struct vmbus_channel *))
|
||||
{
|
||||
channel->chn_rescind_callback = chn_rescind_cb;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(vmbus_set_chn_rescind_callback);
|
||||
|
|
|
@ -88,8 +88,16 @@ static int vmbus_negotiate_version(struct vmbus_channel_msginfo *msginfo,
|
|||
* This has been the behavior pre-win8. This is not
|
||||
* perf issue and having all channel messages delivered on CPU 0
|
||||
* would be ok.
|
||||
* For post win8 hosts, we support receiving channel messagges on
|
||||
* all the CPUs. This is needed for kexec to work correctly where
|
||||
* the CPU attempting to connect may not be CPU 0.
|
||||
*/
|
||||
msg->target_vcpu = 0;
|
||||
if (version >= VERSION_WIN8_1) {
|
||||
msg->target_vcpu = hv_context.vp_index[get_cpu()];
|
||||
put_cpu();
|
||||
} else {
|
||||
msg->target_vcpu = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Add to list before we send the request since we may
|
||||
|
@ -236,7 +244,7 @@ void vmbus_disconnect(void)
|
|||
/*
|
||||
* First send the unload request to the host.
|
||||
*/
|
||||
vmbus_initiate_unload();
|
||||
vmbus_initiate_unload(false);
|
||||
|
||||
if (vmbus_connection.work_queue) {
|
||||
drain_workqueue(vmbus_connection.work_queue);
|
||||
|
@ -288,7 +296,8 @@ struct vmbus_channel *relid2channel(u32 relid)
|
|||
struct list_head *cur, *tmp;
|
||||
struct vmbus_channel *cur_sc;
|
||||
|
||||
mutex_lock(&vmbus_connection.channel_mutex);
|
||||
BUG_ON(!mutex_is_locked(&vmbus_connection.channel_mutex));
|
||||
|
||||
list_for_each_entry(channel, &vmbus_connection.chn_list, listentry) {
|
||||
if (channel->offermsg.child_relid == relid) {
|
||||
found_channel = channel;
|
||||
|
@ -307,7 +316,6 @@ struct vmbus_channel *relid2channel(u32 relid)
|
|||
}
|
||||
}
|
||||
}
|
||||
mutex_unlock(&vmbus_connection.channel_mutex);
|
||||
|
||||
return found_channel;
|
||||
}
|
||||
|
@ -474,7 +482,7 @@ int vmbus_post_msg(void *buffer, size_t buflen)
|
|||
/*
|
||||
* vmbus_set_event - Send an event notification to the parent
|
||||
*/
|
||||
int vmbus_set_event(struct vmbus_channel *channel)
|
||||
void vmbus_set_event(struct vmbus_channel *channel)
|
||||
{
|
||||
u32 child_relid = channel->offermsg.child_relid;
|
||||
|
||||
|
@ -485,5 +493,5 @@ int vmbus_set_event(struct vmbus_channel *channel)
|
|||
(child_relid >> 5));
|
||||
}
|
||||
|
||||
return hv_signal_event(channel->sig_event);
|
||||
hv_do_hypercall(HVCALL_SIGNAL_EVENT, channel->sig_event, NULL);
|
||||
}
|
||||
|
|
|
@ -204,6 +204,8 @@ int hv_init(void)
|
|||
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);
|
||||
|
||||
|
@ -295,8 +297,14 @@ void hv_cleanup(void)
|
|||
* Cleanup the TSC page based CS.
|
||||
*/
|
||||
if (ms_hyperv.features & HV_X64_MSR_REFERENCE_TSC_AVAILABLE) {
|
||||
clocksource_change_rating(&hyperv_cs_tsc, 10);
|
||||
clocksource_unregister(&hyperv_cs_tsc);
|
||||
/*
|
||||
* 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);
|
||||
|
@ -337,22 +345,6 @@ int hv_post_message(union hv_connection_id connection_id,
|
|||
return status & 0xFFFF;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* hv_signal_event -
|
||||
* Signal an event on the specified connection using the hypervisor event IPC.
|
||||
*
|
||||
* This involves a hypercall.
|
||||
*/
|
||||
int hv_signal_event(void *con_id)
|
||||
{
|
||||
u64 status;
|
||||
|
||||
status = hv_do_hypercall(HVCALL_SIGNAL_EVENT, con_id, NULL);
|
||||
|
||||
return status & 0xFFFF;
|
||||
}
|
||||
|
||||
static int hv_ce_set_next_event(unsigned long delta,
|
||||
struct clock_event_device *evt)
|
||||
{
|
||||
|
@ -425,6 +417,13 @@ int hv_synic_alloc(void)
|
|||
}
|
||||
tasklet_init(hv_context.event_dpc[cpu], vmbus_on_event, cpu);
|
||||
|
||||
hv_context.msg_dpc[cpu] = kmalloc(size, GFP_ATOMIC);
|
||||
if (hv_context.msg_dpc[cpu] == NULL) {
|
||||
pr_err("Unable to allocate event dpc\n");
|
||||
goto err;
|
||||
}
|
||||
tasklet_init(hv_context.msg_dpc[cpu], vmbus_on_msg_dpc, cpu);
|
||||
|
||||
hv_context.clk_evt[cpu] = kzalloc(ced_size, GFP_ATOMIC);
|
||||
if (hv_context.clk_evt[cpu] == NULL) {
|
||||
pr_err("Unable to allocate clock event device\n");
|
||||
|
@ -466,6 +465,7 @@ err:
|
|||
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]);
|
||||
|
|
|
@ -251,7 +251,6 @@ void hv_fcopy_onchannelcallback(void *context)
|
|||
*/
|
||||
|
||||
fcopy_transaction.recv_len = recvlen;
|
||||
fcopy_transaction.recv_channel = channel;
|
||||
fcopy_transaction.recv_req_id = requestid;
|
||||
fcopy_transaction.fcopy_msg = fcopy_msg;
|
||||
|
||||
|
@ -317,6 +316,7 @@ static void fcopy_on_reset(void)
|
|||
int hv_fcopy_init(struct hv_util_service *srv)
|
||||
{
|
||||
recv_buffer = srv->recv_buffer;
|
||||
fcopy_transaction.recv_channel = srv->channel;
|
||||
|
||||
/*
|
||||
* When this driver loads, the user level daemon that
|
||||
|
|
|
@ -639,7 +639,6 @@ void hv_kvp_onchannelcallback(void *context)
|
|||
*/
|
||||
|
||||
kvp_transaction.recv_len = recvlen;
|
||||
kvp_transaction.recv_channel = channel;
|
||||
kvp_transaction.recv_req_id = requestid;
|
||||
kvp_transaction.kvp_msg = kvp_msg;
|
||||
|
||||
|
@ -688,6 +687,7 @@ int
|
|||
hv_kvp_init(struct hv_util_service *srv)
|
||||
{
|
||||
recv_buffer = srv->recv_buffer;
|
||||
kvp_transaction.recv_channel = srv->channel;
|
||||
|
||||
/*
|
||||
* When this driver loads, the user level daemon that
|
||||
|
|
|
@ -263,7 +263,6 @@ void hv_vss_onchannelcallback(void *context)
|
|||
*/
|
||||
|
||||
vss_transaction.recv_len = recvlen;
|
||||
vss_transaction.recv_channel = channel;
|
||||
vss_transaction.recv_req_id = requestid;
|
||||
vss_transaction.msg = (struct hv_vss_msg *)vss_msg;
|
||||
|
||||
|
@ -337,6 +336,7 @@ hv_vss_init(struct hv_util_service *srv)
|
|||
return -ENOTSUPP;
|
||||
}
|
||||
recv_buffer = srv->recv_buffer;
|
||||
vss_transaction.recv_channel = srv->channel;
|
||||
|
||||
/*
|
||||
* When this driver loads, the user level daemon that
|
||||
|
|
|
@ -322,6 +322,7 @@ static int util_probe(struct hv_device *dev,
|
|||
srv->recv_buffer = kmalloc(PAGE_SIZE * 4, GFP_KERNEL);
|
||||
if (!srv->recv_buffer)
|
||||
return -ENOMEM;
|
||||
srv->channel = dev->channel;
|
||||
if (srv->util_init) {
|
||||
ret = srv->util_init(srv);
|
||||
if (ret) {
|
||||
|
|
|
@ -310,6 +310,9 @@ struct hvutil_transport *hvutil_transport_init(const char *name,
|
|||
return hvt;
|
||||
|
||||
err_free_hvt:
|
||||
spin_lock(&hvt_list_lock);
|
||||
list_del(&hvt->list);
|
||||
spin_unlock(&hvt_list_lock);
|
||||
kfree(hvt);
|
||||
return NULL;
|
||||
}
|
||||
|
|
|
@ -443,10 +443,11 @@ struct hv_context {
|
|||
u32 vp_index[NR_CPUS];
|
||||
/*
|
||||
* Starting with win8, we can take channel interrupts on any CPU;
|
||||
* we will manage the tasklet that handles events on a per 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.
|
||||
|
@ -495,8 +496,6 @@ extern int hv_post_message(union hv_connection_id connection_id,
|
|||
enum hv_message_type message_type,
|
||||
void *payload, size_t payload_size);
|
||||
|
||||
extern int hv_signal_event(void *con_id);
|
||||
|
||||
extern int hv_synic_alloc(void);
|
||||
|
||||
extern void hv_synic_free(void);
|
||||
|
@ -525,7 +524,7 @@ void hv_ringbuffer_cleanup(struct hv_ring_buffer_info *ring_info);
|
|||
|
||||
int hv_ringbuffer_write(struct hv_ring_buffer_info *ring_info,
|
||||
struct kvec *kv_list,
|
||||
u32 kv_count, bool *signal);
|
||||
u32 kv_count, bool *signal, bool lock);
|
||||
|
||||
int hv_ringbuffer_read(struct hv_ring_buffer_info *inring_info,
|
||||
void *buffer, u32 buflen, u32 *buffer_actual_len,
|
||||
|
@ -620,6 +619,30 @@ struct vmbus_channel_message_table_entry {
|
|||
extern struct vmbus_channel_message_table_entry
|
||||
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)
|
||||
{
|
||||
msg->header.message_type = HVMSG_NONE;
|
||||
|
||||
/*
|
||||
* 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 */
|
||||
|
||||
struct hv_device *vmbus_device_create(const uuid_le *type,
|
||||
|
@ -644,9 +667,10 @@ void vmbus_disconnect(void);
|
|||
|
||||
int vmbus_post_msg(void *buffer, size_t buflen);
|
||||
|
||||
int vmbus_set_event(struct vmbus_channel *channel);
|
||||
void vmbus_set_event(struct vmbus_channel *channel);
|
||||
|
||||
void vmbus_on_event(unsigned long data);
|
||||
void vmbus_on_msg_dpc(unsigned long data);
|
||||
|
||||
int hv_kvp_init(struct hv_util_service *);
|
||||
void hv_kvp_deinit(void);
|
||||
|
@ -659,7 +683,7 @@ void hv_vss_onchannelcallback(void *);
|
|||
int hv_fcopy_init(struct hv_util_service *);
|
||||
void hv_fcopy_deinit(void);
|
||||
void hv_fcopy_onchannelcallback(void *);
|
||||
void vmbus_initiate_unload(void);
|
||||
void vmbus_initiate_unload(bool crash);
|
||||
|
||||
static inline void hv_poll_channel(struct vmbus_channel *channel,
|
||||
void (*cb)(void *))
|
||||
|
|
|
@ -314,7 +314,7 @@ void hv_ringbuffer_cleanup(struct hv_ring_buffer_info *ring_info)
|
|||
|
||||
/* Write to the ring buffer. */
|
||||
int hv_ringbuffer_write(struct hv_ring_buffer_info *outring_info,
|
||||
struct kvec *kv_list, u32 kv_count, bool *signal)
|
||||
struct kvec *kv_list, u32 kv_count, bool *signal, bool lock)
|
||||
{
|
||||
int i = 0;
|
||||
u32 bytes_avail_towrite;
|
||||
|
@ -324,14 +324,15 @@ int hv_ringbuffer_write(struct hv_ring_buffer_info *outring_info,
|
|||
u32 next_write_location;
|
||||
u32 old_write;
|
||||
u64 prev_indices = 0;
|
||||
unsigned long flags;
|
||||
unsigned long flags = 0;
|
||||
|
||||
for (i = 0; i < kv_count; i++)
|
||||
totalbytes_towrite += kv_list[i].iov_len;
|
||||
|
||||
totalbytes_towrite += sizeof(u64);
|
||||
|
||||
spin_lock_irqsave(&outring_info->ring_lock, flags);
|
||||
if (lock)
|
||||
spin_lock_irqsave(&outring_info->ring_lock, flags);
|
||||
|
||||
hv_get_ringbuffer_availbytes(outring_info,
|
||||
&bytes_avail_toread,
|
||||
|
@ -343,7 +344,8 @@ int hv_ringbuffer_write(struct hv_ring_buffer_info *outring_info,
|
|||
* is empty since the read index == write index.
|
||||
*/
|
||||
if (bytes_avail_towrite <= totalbytes_towrite) {
|
||||
spin_unlock_irqrestore(&outring_info->ring_lock, flags);
|
||||
if (lock)
|
||||
spin_unlock_irqrestore(&outring_info->ring_lock, flags);
|
||||
return -EAGAIN;
|
||||
}
|
||||
|
||||
|
@ -374,7 +376,8 @@ int hv_ringbuffer_write(struct hv_ring_buffer_info *outring_info,
|
|||
hv_set_next_write_location(outring_info, next_write_location);
|
||||
|
||||
|
||||
spin_unlock_irqrestore(&outring_info->ring_lock, flags);
|
||||
if (lock)
|
||||
spin_unlock_irqrestore(&outring_info->ring_lock, flags);
|
||||
|
||||
*signal = hv_need_to_signal(old_write, outring_info);
|
||||
return 0;
|
||||
|
@ -388,7 +391,6 @@ int hv_ringbuffer_read(struct hv_ring_buffer_info *inring_info,
|
|||
u32 bytes_avail_toread;
|
||||
u32 next_read_location = 0;
|
||||
u64 prev_indices = 0;
|
||||
unsigned long flags;
|
||||
struct vmpacket_descriptor desc;
|
||||
u32 offset;
|
||||
u32 packetlen;
|
||||
|
@ -397,7 +399,6 @@ int hv_ringbuffer_read(struct hv_ring_buffer_info *inring_info,
|
|||
if (buflen <= 0)
|
||||
return -EINVAL;
|
||||
|
||||
spin_lock_irqsave(&inring_info->ring_lock, flags);
|
||||
|
||||
*buffer_actual_len = 0;
|
||||
*requestid = 0;
|
||||
|
@ -412,7 +413,7 @@ int hv_ringbuffer_read(struct hv_ring_buffer_info *inring_info,
|
|||
* No error is set when there is even no header, drivers are
|
||||
* supposed to analyze buffer_actual_len.
|
||||
*/
|
||||
goto out_unlock;
|
||||
return ret;
|
||||
}
|
||||
|
||||
next_read_location = hv_get_next_read_location(inring_info);
|
||||
|
@ -425,15 +426,11 @@ int hv_ringbuffer_read(struct hv_ring_buffer_info *inring_info,
|
|||
*buffer_actual_len = packetlen;
|
||||
*requestid = desc.trans_id;
|
||||
|
||||
if (bytes_avail_toread < packetlen + offset) {
|
||||
ret = -EAGAIN;
|
||||
goto out_unlock;
|
||||
}
|
||||
if (bytes_avail_toread < packetlen + offset)
|
||||
return -EAGAIN;
|
||||
|
||||
if (packetlen > buflen) {
|
||||
ret = -ENOBUFS;
|
||||
goto out_unlock;
|
||||
}
|
||||
if (packetlen > buflen)
|
||||
return -ENOBUFS;
|
||||
|
||||
next_read_location =
|
||||
hv_get_next_readlocation_withoffset(inring_info, offset);
|
||||
|
@ -460,7 +457,5 @@ int hv_ringbuffer_read(struct hv_ring_buffer_info *inring_info,
|
|||
|
||||
*signal = hv_need_to_signal_on_read(bytes_avail_towrite, inring_info);
|
||||
|
||||
out_unlock:
|
||||
spin_unlock_irqrestore(&inring_info->ring_lock, flags);
|
||||
return ret;
|
||||
}
|
||||
|
|
|
@ -45,7 +45,6 @@
|
|||
|
||||
static struct acpi_device *hv_acpi_dev;
|
||||
|
||||
static struct tasklet_struct msg_dpc;
|
||||
static struct completion probe_event;
|
||||
|
||||
|
||||
|
@ -477,6 +476,24 @@ static ssize_t channel_vp_mapping_show(struct device *dev,
|
|||
}
|
||||
static DEVICE_ATTR_RO(channel_vp_mapping);
|
||||
|
||||
static ssize_t vendor_show(struct device *dev,
|
||||
struct device_attribute *dev_attr,
|
||||
char *buf)
|
||||
{
|
||||
struct hv_device *hv_dev = device_to_hv_device(dev);
|
||||
return sprintf(buf, "0x%x\n", hv_dev->vendor_id);
|
||||
}
|
||||
static DEVICE_ATTR_RO(vendor);
|
||||
|
||||
static ssize_t device_show(struct device *dev,
|
||||
struct device_attribute *dev_attr,
|
||||
char *buf)
|
||||
{
|
||||
struct hv_device *hv_dev = device_to_hv_device(dev);
|
||||
return sprintf(buf, "0x%x\n", hv_dev->device_id);
|
||||
}
|
||||
static DEVICE_ATTR_RO(device);
|
||||
|
||||
/* Set up per device attributes in /sys/bus/vmbus/devices/<bus device> */
|
||||
static struct attribute *vmbus_attrs[] = {
|
||||
&dev_attr_id.attr,
|
||||
|
@ -502,6 +519,8 @@ static struct attribute *vmbus_attrs[] = {
|
|||
&dev_attr_in_read_bytes_avail.attr,
|
||||
&dev_attr_in_write_bytes_avail.attr,
|
||||
&dev_attr_channel_vp_mapping.attr,
|
||||
&dev_attr_vendor.attr,
|
||||
&dev_attr_device.attr,
|
||||
NULL,
|
||||
};
|
||||
ATTRIBUTE_GROUPS(vmbus);
|
||||
|
@ -562,6 +581,10 @@ static int vmbus_match(struct device *device, struct device_driver *driver)
|
|||
struct hv_driver *drv = drv_to_hv_drv(driver);
|
||||
struct hv_device *hv_dev = device_to_hv_device(device);
|
||||
|
||||
/* The hv_sock driver handles all hv_sock offers. */
|
||||
if (is_hvsock_channel(hv_dev->channel))
|
||||
return drv->hvsock;
|
||||
|
||||
if (hv_vmbus_get_id(drv->id_table, &hv_dev->dev_type))
|
||||
return 1;
|
||||
|
||||
|
@ -685,28 +708,10 @@ static void hv_process_timer_expiration(struct hv_message *msg, int cpu)
|
|||
if (dev->event_handler)
|
||||
dev->event_handler(dev);
|
||||
|
||||
msg->header.message_type = HVMSG_NONE;
|
||||
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
vmbus_signal_eom(msg);
|
||||
}
|
||||
|
||||
static void vmbus_on_msg_dpc(unsigned long data)
|
||||
void vmbus_on_msg_dpc(unsigned long data)
|
||||
{
|
||||
int cpu = smp_processor_id();
|
||||
void *page_addr = hv_context.synic_message_page[cpu];
|
||||
|
@ -716,52 +721,32 @@ static void vmbus_on_msg_dpc(unsigned long data)
|
|||
struct vmbus_channel_message_table_entry *entry;
|
||||
struct onmessage_work_context *ctx;
|
||||
|
||||
while (1) {
|
||||
if (msg->header.message_type == HVMSG_NONE)
|
||||
/* no msg */
|
||||
break;
|
||||
if (msg->header.message_type == HVMSG_NONE)
|
||||
/* no msg */
|
||||
return;
|
||||
|
||||
hdr = (struct vmbus_channel_message_header *)msg->u.payload;
|
||||
hdr = (struct vmbus_channel_message_header *)msg->u.payload;
|
||||
|
||||
if (hdr->msgtype >= CHANNELMSG_COUNT) {
|
||||
WARN_ONCE(1, "unknown msgtype=%d\n", hdr->msgtype);
|
||||
goto msg_handled;
|
||||
}
|
||||
if (hdr->msgtype >= CHANNELMSG_COUNT) {
|
||||
WARN_ONCE(1, "unknown msgtype=%d\n", hdr->msgtype);
|
||||
goto msg_handled;
|
||||
}
|
||||
|
||||
entry = &channel_message_table[hdr->msgtype];
|
||||
if (entry->handler_type == VMHT_BLOCKING) {
|
||||
ctx = kmalloc(sizeof(*ctx), GFP_ATOMIC);
|
||||
if (ctx == NULL)
|
||||
continue;
|
||||
entry = &channel_message_table[hdr->msgtype];
|
||||
if (entry->handler_type == VMHT_BLOCKING) {
|
||||
ctx = kmalloc(sizeof(*ctx), GFP_ATOMIC);
|
||||
if (ctx == NULL)
|
||||
return;
|
||||
|
||||
INIT_WORK(&ctx->work, vmbus_onmessage_work);
|
||||
memcpy(&ctx->msg, msg, sizeof(*msg));
|
||||
INIT_WORK(&ctx->work, vmbus_onmessage_work);
|
||||
memcpy(&ctx->msg, msg, sizeof(*msg));
|
||||
|
||||
queue_work(vmbus_connection.work_queue, &ctx->work);
|
||||
} else
|
||||
entry->message_handler(hdr);
|
||||
queue_work(vmbus_connection.work_queue, &ctx->work);
|
||||
} else
|
||||
entry->message_handler(hdr);
|
||||
|
||||
msg_handled:
|
||||
msg->header.message_type = HVMSG_NONE;
|
||||
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
vmbus_signal_eom(msg);
|
||||
}
|
||||
|
||||
static void vmbus_isr(void)
|
||||
|
@ -814,7 +799,7 @@ static void vmbus_isr(void)
|
|||
if (msg->header.message_type == HVMSG_TIMER_EXPIRED)
|
||||
hv_process_timer_expiration(msg, cpu);
|
||||
else
|
||||
tasklet_schedule(&msg_dpc);
|
||||
tasklet_schedule(hv_context.msg_dpc[cpu]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -838,8 +823,6 @@ static int vmbus_bus_init(void)
|
|||
return ret;
|
||||
}
|
||||
|
||||
tasklet_init(&msg_dpc, vmbus_on_msg_dpc, 0);
|
||||
|
||||
ret = bus_register(&hv_bus);
|
||||
if (ret)
|
||||
goto err_cleanup;
|
||||
|
@ -957,6 +940,7 @@ struct hv_device *vmbus_device_create(const uuid_le *type,
|
|||
memcpy(&child_device_obj->dev_type, type, sizeof(uuid_le));
|
||||
memcpy(&child_device_obj->dev_instance, instance,
|
||||
sizeof(uuid_le));
|
||||
child_device_obj->vendor_id = 0x1414; /* MSFT vendor ID */
|
||||
|
||||
|
||||
return child_device_obj;
|
||||
|
@ -1268,7 +1252,7 @@ static void hv_kexec_handler(void)
|
|||
int cpu;
|
||||
|
||||
hv_synic_clockevents_cleanup();
|
||||
vmbus_initiate_unload();
|
||||
vmbus_initiate_unload(false);
|
||||
for_each_online_cpu(cpu)
|
||||
smp_call_function_single(cpu, hv_synic_cleanup, NULL, 1);
|
||||
hv_cleanup();
|
||||
|
@ -1276,7 +1260,7 @@ static void hv_kexec_handler(void)
|
|||
|
||||
static void hv_crash_handler(struct pt_regs *regs)
|
||||
{
|
||||
vmbus_initiate_unload();
|
||||
vmbus_initiate_unload(true);
|
||||
/*
|
||||
* In crash handler we can't schedule synic cleanup for all CPUs,
|
||||
* doing the cleanup for current CPU only. This should be sufficient
|
||||
|
@ -1334,7 +1318,8 @@ static void __exit vmbus_exit(void)
|
|||
hv_synic_clockevents_cleanup();
|
||||
vmbus_disconnect();
|
||||
hv_remove_vmbus_irq();
|
||||
tasklet_kill(&msg_dpc);
|
||||
for_each_online_cpu(cpu)
|
||||
tasklet_kill(hv_context.msg_dpc[cpu]);
|
||||
vmbus_free_channels();
|
||||
if (ms_hyperv.misc_features & HV_FEATURE_GUEST_CRASH_MSR_AVAILABLE) {
|
||||
unregister_die_notifier(&hyperv_die_block);
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
menuconfig CORESIGHT
|
||||
bool "CoreSight Tracing Support"
|
||||
select ARM_AMBA
|
||||
select PERF_EVENTS
|
||||
help
|
||||
This framework provides a kernel interface for the CoreSight debug
|
||||
and trace drivers to register themselves with. It's intended to build
|
||||
|
|
|
@ -8,6 +8,8 @@ obj-$(CONFIG_CORESIGHT_SINK_TPIU) += coresight-tpiu.o
|
|||
obj-$(CONFIG_CORESIGHT_SINK_ETBV10) += coresight-etb10.o
|
||||
obj-$(CONFIG_CORESIGHT_LINKS_AND_SINKS) += coresight-funnel.o \
|
||||
coresight-replicator.o
|
||||
obj-$(CONFIG_CORESIGHT_SOURCE_ETM3X) += coresight-etm3x.o coresight-etm-cp14.o
|
||||
obj-$(CONFIG_CORESIGHT_SOURCE_ETM3X) += coresight-etm3x.o coresight-etm-cp14.o \
|
||||
coresight-etm3x-sysfs.o \
|
||||
coresight-etm-perf.o
|
||||
obj-$(CONFIG_CORESIGHT_SOURCE_ETM4X) += coresight-etm4x.o
|
||||
obj-$(CONFIG_CORESIGHT_QCOM_REPLICATOR) += coresight-replicator-qcom.o
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* Description: CoreSight Embedded Trace Buffer 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 and
|
||||
|
@ -10,8 +12,8 @@
|
|||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <asm/local.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/device.h>
|
||||
|
@ -27,6 +29,11 @@
|
|||
#include <linux/coresight.h>
|
||||
#include <linux/amba/bus.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/circ_buf.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/perf_event.h>
|
||||
|
||||
#include <asm/local.h>
|
||||
|
||||
#include "coresight-priv.h"
|
||||
|
||||
|
@ -63,6 +70,26 @@
|
|||
#define ETB_FFSR_BIT 1
|
||||
#define ETB_FRAME_SIZE_WORDS 4
|
||||
|
||||
/**
|
||||
* struct cs_buffer - keep track of a recording session' specifics
|
||||
* @cur: index of the current buffer
|
||||
* @nr_pages: max number of pages granted to us
|
||||
* @offset: offset within the current buffer
|
||||
* @data_size: how much we collected in this run
|
||||
* @lost: other than zero if we had a HW buffer wrap around
|
||||
* @snapshot: is this run in snapshot mode
|
||||
* @data_pages: a handle the ring buffer
|
||||
*/
|
||||
struct cs_buffers {
|
||||
unsigned int cur;
|
||||
unsigned int nr_pages;
|
||||
unsigned long offset;
|
||||
local_t data_size;
|
||||
local_t lost;
|
||||
bool snapshot;
|
||||
void **data_pages;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct etb_drvdata - specifics associated to an ETB component
|
||||
* @base: memory mapped base address for this component.
|
||||
|
@ -71,10 +98,10 @@
|
|||
* @csdev: component vitals needed by the framework.
|
||||
* @miscdev: specifics to handle "/dev/xyz.etb" entry.
|
||||
* @spinlock: only one at a time pls.
|
||||
* @in_use: synchronise user space access to etb buffer.
|
||||
* @reading: synchronise user space access to etb buffer.
|
||||
* @mode: this ETB is being used.
|
||||
* @buf: area of memory where ETB buffer content gets sent.
|
||||
* @buffer_depth: size of @buf.
|
||||
* @enable: this ETB is being used.
|
||||
* @trigger_cntr: amount of words to store after a trigger.
|
||||
*/
|
||||
struct etb_drvdata {
|
||||
|
@ -84,10 +111,10 @@ struct etb_drvdata {
|
|||
struct coresight_device *csdev;
|
||||
struct miscdevice miscdev;
|
||||
spinlock_t spinlock;
|
||||
atomic_t in_use;
|
||||
local_t reading;
|
||||
local_t mode;
|
||||
u8 *buf;
|
||||
u32 buffer_depth;
|
||||
bool enable;
|
||||
u32 trigger_cntr;
|
||||
};
|
||||
|
||||
|
@ -132,18 +159,31 @@ static void etb_enable_hw(struct etb_drvdata *drvdata)
|
|||
CS_LOCK(drvdata->base);
|
||||
}
|
||||
|
||||
static int etb_enable(struct coresight_device *csdev)
|
||||
static int etb_enable(struct coresight_device *csdev, u32 mode)
|
||||
{
|
||||
struct etb_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
u32 val;
|
||||
unsigned long flags;
|
||||
struct etb_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
pm_runtime_get_sync(drvdata->dev);
|
||||
val = local_cmpxchg(&drvdata->mode,
|
||||
CS_MODE_DISABLED, mode);
|
||||
/*
|
||||
* When accessing from Perf, a HW buffer can be handled
|
||||
* by a single trace entity. In sysFS mode many tracers
|
||||
* can be logging to the same HW buffer.
|
||||
*/
|
||||
if (val == CS_MODE_PERF)
|
||||
return -EBUSY;
|
||||
|
||||
/* Nothing to do, the tracer is already enabled. */
|
||||
if (val == CS_MODE_SYSFS)
|
||||
goto out;
|
||||
|
||||
spin_lock_irqsave(&drvdata->spinlock, flags);
|
||||
etb_enable_hw(drvdata);
|
||||
drvdata->enable = true;
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
|
||||
out:
|
||||
dev_info(drvdata->dev, "ETB enabled\n");
|
||||
return 0;
|
||||
}
|
||||
|
@ -244,17 +284,225 @@ static void etb_disable(struct coresight_device *csdev)
|
|||
spin_lock_irqsave(&drvdata->spinlock, flags);
|
||||
etb_disable_hw(drvdata);
|
||||
etb_dump_hw(drvdata);
|
||||
drvdata->enable = false;
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
|
||||
pm_runtime_put(drvdata->dev);
|
||||
local_set(&drvdata->mode, CS_MODE_DISABLED);
|
||||
|
||||
dev_info(drvdata->dev, "ETB disabled\n");
|
||||
}
|
||||
|
||||
static void *etb_alloc_buffer(struct coresight_device *csdev, int cpu,
|
||||
void **pages, int nr_pages, bool overwrite)
|
||||
{
|
||||
int node;
|
||||
struct cs_buffers *buf;
|
||||
|
||||
if (cpu == -1)
|
||||
cpu = smp_processor_id();
|
||||
node = cpu_to_node(cpu);
|
||||
|
||||
buf = kzalloc_node(sizeof(struct cs_buffers), GFP_KERNEL, node);
|
||||
if (!buf)
|
||||
return NULL;
|
||||
|
||||
buf->snapshot = overwrite;
|
||||
buf->nr_pages = nr_pages;
|
||||
buf->data_pages = pages;
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
static void etb_free_buffer(void *config)
|
||||
{
|
||||
struct cs_buffers *buf = config;
|
||||
|
||||
kfree(buf);
|
||||
}
|
||||
|
||||
static int etb_set_buffer(struct coresight_device *csdev,
|
||||
struct perf_output_handle *handle,
|
||||
void *sink_config)
|
||||
{
|
||||
int ret = 0;
|
||||
unsigned long head;
|
||||
struct cs_buffers *buf = sink_config;
|
||||
|
||||
/* wrap head around to the amount of space we have */
|
||||
head = handle->head & ((buf->nr_pages << PAGE_SHIFT) - 1);
|
||||
|
||||
/* find the page to write to */
|
||||
buf->cur = head / PAGE_SIZE;
|
||||
|
||||
/* and offset within that page */
|
||||
buf->offset = head % PAGE_SIZE;
|
||||
|
||||
local_set(&buf->data_size, 0);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static unsigned long etb_reset_buffer(struct coresight_device *csdev,
|
||||
struct perf_output_handle *handle,
|
||||
void *sink_config, bool *lost)
|
||||
{
|
||||
unsigned long size = 0;
|
||||
struct cs_buffers *buf = sink_config;
|
||||
|
||||
if (buf) {
|
||||
/*
|
||||
* In snapshot mode ->data_size holds the new address of the
|
||||
* ring buffer's head. The size itself is the whole address
|
||||
* range since we want the latest information.
|
||||
*/
|
||||
if (buf->snapshot)
|
||||
handle->head = local_xchg(&buf->data_size,
|
||||
buf->nr_pages << PAGE_SHIFT);
|
||||
|
||||
/*
|
||||
* Tell the tracer PMU how much we got in this run and if
|
||||
* something went wrong along the way. Nobody else can use
|
||||
* this cs_buffers instance until we are done. As such
|
||||
* resetting parameters here and squaring off with the ring
|
||||
* buffer API in the tracer PMU is fine.
|
||||
*/
|
||||
*lost = !!local_xchg(&buf->lost, 0);
|
||||
size = local_xchg(&buf->data_size, 0);
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static void etb_update_buffer(struct coresight_device *csdev,
|
||||
struct perf_output_handle *handle,
|
||||
void *sink_config)
|
||||
{
|
||||
int i, cur;
|
||||
u8 *buf_ptr;
|
||||
u32 read_ptr, write_ptr, capacity;
|
||||
u32 status, read_data, to_read;
|
||||
unsigned long offset;
|
||||
struct cs_buffers *buf = sink_config;
|
||||
struct etb_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
if (!buf)
|
||||
return;
|
||||
|
||||
capacity = drvdata->buffer_depth * ETB_FRAME_SIZE_WORDS;
|
||||
|
||||
CS_UNLOCK(drvdata->base);
|
||||
etb_disable_hw(drvdata);
|
||||
|
||||
/* unit is in words, not bytes */
|
||||
read_ptr = readl_relaxed(drvdata->base + ETB_RAM_READ_POINTER);
|
||||
write_ptr = readl_relaxed(drvdata->base + ETB_RAM_WRITE_POINTER);
|
||||
|
||||
/*
|
||||
* Entries should be aligned to the frame size. If they are not
|
||||
* go back to the last alignement point to give decoding tools a
|
||||
* chance to fix things.
|
||||
*/
|
||||
if (write_ptr % ETB_FRAME_SIZE_WORDS) {
|
||||
dev_err(drvdata->dev,
|
||||
"write_ptr: %lu not aligned to formatter frame size\n",
|
||||
(unsigned long)write_ptr);
|
||||
|
||||
write_ptr &= ~(ETB_FRAME_SIZE_WORDS - 1);
|
||||
local_inc(&buf->lost);
|
||||
}
|
||||
|
||||
/*
|
||||
* Get a hold of the status register and see if a wrap around
|
||||
* has occurred. If so adjust things accordingly. Otherwise
|
||||
* start at the beginning and go until the write pointer has
|
||||
* been reached.
|
||||
*/
|
||||
status = readl_relaxed(drvdata->base + ETB_STATUS_REG);
|
||||
if (status & ETB_STATUS_RAM_FULL) {
|
||||
local_inc(&buf->lost);
|
||||
to_read = capacity;
|
||||
read_ptr = write_ptr;
|
||||
} else {
|
||||
to_read = CIRC_CNT(write_ptr, read_ptr, drvdata->buffer_depth);
|
||||
to_read *= ETB_FRAME_SIZE_WORDS;
|
||||
}
|
||||
|
||||
/*
|
||||
* Make sure we don't overwrite data that hasn't been consumed yet.
|
||||
* It is entirely possible that the HW buffer has more data than the
|
||||
* ring buffer can currently handle. If so adjust the start address
|
||||
* to take only the last traces.
|
||||
*
|
||||
* In snapshot mode we are looking to get the latest traces only and as
|
||||
* such, we don't care about not overwriting data that hasn't been
|
||||
* processed by user space.
|
||||
*/
|
||||
if (!buf->snapshot && to_read > handle->size) {
|
||||
u32 mask = ~(ETB_FRAME_SIZE_WORDS - 1);
|
||||
|
||||
/* The new read pointer must be frame size aligned */
|
||||
to_read -= handle->size & mask;
|
||||
/*
|
||||
* Move the RAM read pointer up, keeping in mind that
|
||||
* everything is in frame size units.
|
||||
*/
|
||||
read_ptr = (write_ptr + drvdata->buffer_depth) -
|
||||
to_read / ETB_FRAME_SIZE_WORDS;
|
||||
/* Wrap around if need be*/
|
||||
read_ptr &= ~(drvdata->buffer_depth - 1);
|
||||
/* let the decoder know we've skipped ahead */
|
||||
local_inc(&buf->lost);
|
||||
}
|
||||
|
||||
/* finally tell HW where we want to start reading from */
|
||||
writel_relaxed(read_ptr, drvdata->base + ETB_RAM_READ_POINTER);
|
||||
|
||||
cur = buf->cur;
|
||||
offset = buf->offset;
|
||||
for (i = 0; i < to_read; i += 4) {
|
||||
buf_ptr = buf->data_pages[cur] + offset;
|
||||
read_data = readl_relaxed(drvdata->base +
|
||||
ETB_RAM_READ_DATA_REG);
|
||||
*buf_ptr++ = read_data >> 0;
|
||||
*buf_ptr++ = read_data >> 8;
|
||||
*buf_ptr++ = read_data >> 16;
|
||||
*buf_ptr++ = read_data >> 24;
|
||||
|
||||
offset += 4;
|
||||
if (offset >= PAGE_SIZE) {
|
||||
offset = 0;
|
||||
cur++;
|
||||
/* wrap around at the end of the buffer */
|
||||
cur &= buf->nr_pages - 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* reset ETB buffer for next run */
|
||||
writel_relaxed(0x0, drvdata->base + ETB_RAM_READ_POINTER);
|
||||
writel_relaxed(0x0, drvdata->base + ETB_RAM_WRITE_POINTER);
|
||||
|
||||
/*
|
||||
* In snapshot mode all we have to do is communicate to
|
||||
* perf_aux_output_end() the address of the current head. In full
|
||||
* trace mode the same function expects a size to move rb->aux_head
|
||||
* forward.
|
||||
*/
|
||||
if (buf->snapshot)
|
||||
local_set(&buf->data_size, (cur * PAGE_SIZE) + offset);
|
||||
else
|
||||
local_add(to_read, &buf->data_size);
|
||||
|
||||
etb_enable_hw(drvdata);
|
||||
CS_LOCK(drvdata->base);
|
||||
}
|
||||
|
||||
static const struct coresight_ops_sink etb_sink_ops = {
|
||||
.enable = etb_enable,
|
||||
.disable = etb_disable,
|
||||
.alloc_buffer = etb_alloc_buffer,
|
||||
.free_buffer = etb_free_buffer,
|
||||
.set_buffer = etb_set_buffer,
|
||||
.reset_buffer = etb_reset_buffer,
|
||||
.update_buffer = etb_update_buffer,
|
||||
};
|
||||
|
||||
static const struct coresight_ops etb_cs_ops = {
|
||||
|
@ -266,7 +514,7 @@ static void etb_dump(struct etb_drvdata *drvdata)
|
|||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&drvdata->spinlock, flags);
|
||||
if (drvdata->enable) {
|
||||
if (local_read(&drvdata->mode) == CS_MODE_SYSFS) {
|
||||
etb_disable_hw(drvdata);
|
||||
etb_dump_hw(drvdata);
|
||||
etb_enable_hw(drvdata);
|
||||
|
@ -281,7 +529,7 @@ static int etb_open(struct inode *inode, struct file *file)
|
|||
struct etb_drvdata *drvdata = container_of(file->private_data,
|
||||
struct etb_drvdata, miscdev);
|
||||
|
||||
if (atomic_cmpxchg(&drvdata->in_use, 0, 1))
|
||||
if (local_cmpxchg(&drvdata->reading, 0, 1))
|
||||
return -EBUSY;
|
||||
|
||||
dev_dbg(drvdata->dev, "%s: successfully opened\n", __func__);
|
||||
|
@ -317,7 +565,7 @@ static int etb_release(struct inode *inode, struct file *file)
|
|||
{
|
||||
struct etb_drvdata *drvdata = container_of(file->private_data,
|
||||
struct etb_drvdata, miscdev);
|
||||
atomic_set(&drvdata->in_use, 0);
|
||||
local_set(&drvdata->reading, 0);
|
||||
|
||||
dev_dbg(drvdata->dev, "%s: released\n", __func__);
|
||||
return 0;
|
||||
|
@ -489,15 +737,6 @@ err_misc_register:
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int etb_remove(struct amba_device *adev)
|
||||
{
|
||||
struct etb_drvdata *drvdata = amba_get_drvdata(adev);
|
||||
|
||||
misc_deregister(&drvdata->miscdev);
|
||||
coresight_unregister(drvdata->csdev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int etb_runtime_suspend(struct device *dev)
|
||||
{
|
||||
|
@ -537,14 +776,10 @@ static struct amba_driver etb_driver = {
|
|||
.name = "coresight-etb10",
|
||||
.owner = THIS_MODULE,
|
||||
.pm = &etb_dev_pm_ops,
|
||||
.suppress_bind_attrs = true,
|
||||
|
||||
},
|
||||
.probe = etb_probe,
|
||||
.remove = etb_remove,
|
||||
.id_table = etb_ids,
|
||||
};
|
||||
|
||||
module_amba_driver(etb_driver);
|
||||
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_DESCRIPTION("CoreSight Embedded Trace Buffer driver");
|
||||
builtin_amba_driver(etb_driver);
|
||||
|
|
|
@ -0,0 +1,393 @@
|
|||
/*
|
||||
* Copyright(C) 2015 Linaro Limited. All rights reserved.
|
||||
* Author: Mathieu Poirier <mathieu.poirier@linaro.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <linux/coresight.h>
|
||||
#include <linux/coresight-pmu.h>
|
||||
#include <linux/cpumask.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/perf_event.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
#include "coresight-priv.h"
|
||||
|
||||
static struct pmu etm_pmu;
|
||||
static bool etm_perf_up;
|
||||
|
||||
/**
|
||||
* struct etm_event_data - Coresight specifics associated to an event
|
||||
* @work: Handle to free allocated memory outside IRQ context.
|
||||
* @mask: Hold the CPU(s) this event was set for.
|
||||
* @snk_config: The sink configuration.
|
||||
* @path: An array of path, each slot for one CPU.
|
||||
*/
|
||||
struct etm_event_data {
|
||||
struct work_struct work;
|
||||
cpumask_t mask;
|
||||
void *snk_config;
|
||||
struct list_head **path;
|
||||
};
|
||||
|
||||
static DEFINE_PER_CPU(struct perf_output_handle, ctx_handle);
|
||||
static DEFINE_PER_CPU(struct coresight_device *, csdev_src);
|
||||
|
||||
/* ETMv3.5/PTM's ETMCR is 'config' */
|
||||
PMU_FORMAT_ATTR(cycacc, "config:" __stringify(ETM_OPT_CYCACC));
|
||||
PMU_FORMAT_ATTR(timestamp, "config:" __stringify(ETM_OPT_TS));
|
||||
|
||||
static struct attribute *etm_config_formats_attr[] = {
|
||||
&format_attr_cycacc.attr,
|
||||
&format_attr_timestamp.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static struct attribute_group etm_pmu_format_group = {
|
||||
.name = "format",
|
||||
.attrs = etm_config_formats_attr,
|
||||
};
|
||||
|
||||
static const struct attribute_group *etm_pmu_attr_groups[] = {
|
||||
&etm_pmu_format_group,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static void etm_event_read(struct perf_event *event) {}
|
||||
|
||||
static int etm_event_init(struct perf_event *event)
|
||||
{
|
||||
if (event->attr.type != etm_pmu.type)
|
||||
return -ENOENT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void free_event_data(struct work_struct *work)
|
||||
{
|
||||
int cpu;
|
||||
cpumask_t *mask;
|
||||
struct etm_event_data *event_data;
|
||||
struct coresight_device *sink;
|
||||
|
||||
event_data = container_of(work, struct etm_event_data, work);
|
||||
mask = &event_data->mask;
|
||||
/*
|
||||
* First deal with the sink configuration. See comment in
|
||||
* etm_setup_aux() about why we take the first available path.
|
||||
*/
|
||||
if (event_data->snk_config) {
|
||||
cpu = cpumask_first(mask);
|
||||
sink = coresight_get_sink(event_data->path[cpu]);
|
||||
if (sink_ops(sink)->free_buffer)
|
||||
sink_ops(sink)->free_buffer(event_data->snk_config);
|
||||
}
|
||||
|
||||
for_each_cpu(cpu, mask) {
|
||||
if (event_data->path[cpu])
|
||||
coresight_release_path(event_data->path[cpu]);
|
||||
}
|
||||
|
||||
kfree(event_data->path);
|
||||
kfree(event_data);
|
||||
}
|
||||
|
||||
static void *alloc_event_data(int cpu)
|
||||
{
|
||||
int size;
|
||||
cpumask_t *mask;
|
||||
struct etm_event_data *event_data;
|
||||
|
||||
/* First get memory for the session's data */
|
||||
event_data = kzalloc(sizeof(struct etm_event_data), GFP_KERNEL);
|
||||
if (!event_data)
|
||||
return NULL;
|
||||
|
||||
/* Make sure nothing disappears under us */
|
||||
get_online_cpus();
|
||||
size = num_online_cpus();
|
||||
|
||||
mask = &event_data->mask;
|
||||
if (cpu != -1)
|
||||
cpumask_set_cpu(cpu, mask);
|
||||
else
|
||||
cpumask_copy(mask, cpu_online_mask);
|
||||
put_online_cpus();
|
||||
|
||||
/*
|
||||
* Each CPU has a single path between source and destination. As such
|
||||
* allocate an array using CPU numbers as indexes. That way a path
|
||||
* for any CPU can easily be accessed at any given time. We proceed
|
||||
* the same way for sessions involving a single CPU. The cost of
|
||||
* unused memory when dealing with single CPU trace scenarios is small
|
||||
* compared to the cost of searching through an optimized array.
|
||||
*/
|
||||
event_data->path = kcalloc(size,
|
||||
sizeof(struct list_head *), GFP_KERNEL);
|
||||
if (!event_data->path) {
|
||||
kfree(event_data);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return event_data;
|
||||
}
|
||||
|
||||
static void etm_free_aux(void *data)
|
||||
{
|
||||
struct etm_event_data *event_data = data;
|
||||
|
||||
schedule_work(&event_data->work);
|
||||
}
|
||||
|
||||
static void *etm_setup_aux(int event_cpu, void **pages,
|
||||
int nr_pages, bool overwrite)
|
||||
{
|
||||
int cpu;
|
||||
cpumask_t *mask;
|
||||
struct coresight_device *sink;
|
||||
struct etm_event_data *event_data = NULL;
|
||||
|
||||
event_data = alloc_event_data(event_cpu);
|
||||
if (!event_data)
|
||||
return NULL;
|
||||
|
||||
INIT_WORK(&event_data->work, free_event_data);
|
||||
|
||||
mask = &event_data->mask;
|
||||
|
||||
/* Setup the path for each CPU in a trace session */
|
||||
for_each_cpu(cpu, mask) {
|
||||
struct coresight_device *csdev;
|
||||
|
||||
csdev = per_cpu(csdev_src, cpu);
|
||||
if (!csdev)
|
||||
goto err;
|
||||
|
||||
/*
|
||||
* Building a path doesn't enable it, it simply builds a
|
||||
* list of devices from source to sink that can be
|
||||
* referenced later when the path is actually needed.
|
||||
*/
|
||||
event_data->path[cpu] = coresight_build_path(csdev);
|
||||
if (!event_data->path[cpu])
|
||||
goto err;
|
||||
}
|
||||
|
||||
/*
|
||||
* In theory nothing prevent tracers in a trace session from being
|
||||
* associated with different sinks, nor having a sink per tracer. But
|
||||
* until we have HW with this kind of topology and a way to convey
|
||||
* sink assignement from the perf cmd line we need to assume tracers
|
||||
* in a trace session are using the same sink. Therefore pick the sink
|
||||
* found at the end of the first available path.
|
||||
*/
|
||||
cpu = cpumask_first(mask);
|
||||
/* Grab the sink at the end of the path */
|
||||
sink = coresight_get_sink(event_data->path[cpu]);
|
||||
if (!sink)
|
||||
goto err;
|
||||
|
||||
if (!sink_ops(sink)->alloc_buffer)
|
||||
goto err;
|
||||
|
||||
/* Get the AUX specific data from the sink buffer */
|
||||
event_data->snk_config =
|
||||
sink_ops(sink)->alloc_buffer(sink, cpu, pages,
|
||||
nr_pages, overwrite);
|
||||
if (!event_data->snk_config)
|
||||
goto err;
|
||||
|
||||
out:
|
||||
return event_data;
|
||||
|
||||
err:
|
||||
etm_free_aux(event_data);
|
||||
event_data = NULL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
static void etm_event_start(struct perf_event *event, int flags)
|
||||
{
|
||||
int cpu = smp_processor_id();
|
||||
struct etm_event_data *event_data;
|
||||
struct perf_output_handle *handle = this_cpu_ptr(&ctx_handle);
|
||||
struct coresight_device *sink, *csdev = per_cpu(csdev_src, cpu);
|
||||
|
||||
if (!csdev)
|
||||
goto fail;
|
||||
|
||||
/*
|
||||
* Deal with the ring buffer API and get a handle on the
|
||||
* session's information.
|
||||
*/
|
||||
event_data = perf_aux_output_begin(handle, event);
|
||||
if (!event_data)
|
||||
goto fail;
|
||||
|
||||
/* We need a sink, no need to continue without one */
|
||||
sink = coresight_get_sink(event_data->path[cpu]);
|
||||
if (WARN_ON_ONCE(!sink || !sink_ops(sink)->set_buffer))
|
||||
goto fail_end_stop;
|
||||
|
||||
/* Configure the sink */
|
||||
if (sink_ops(sink)->set_buffer(sink, handle,
|
||||
event_data->snk_config))
|
||||
goto fail_end_stop;
|
||||
|
||||
/* Nothing will happen without a path */
|
||||
if (coresight_enable_path(event_data->path[cpu], CS_MODE_PERF))
|
||||
goto fail_end_stop;
|
||||
|
||||
/* Tell the perf core the event is alive */
|
||||
event->hw.state = 0;
|
||||
|
||||
/* Finally enable the tracer */
|
||||
if (source_ops(csdev)->enable(csdev, &event->attr, CS_MODE_PERF))
|
||||
goto fail_end_stop;
|
||||
|
||||
out:
|
||||
return;
|
||||
|
||||
fail_end_stop:
|
||||
perf_aux_output_end(handle, 0, true);
|
||||
fail:
|
||||
event->hw.state = PERF_HES_STOPPED;
|
||||
goto out;
|
||||
}
|
||||
|
||||
static void etm_event_stop(struct perf_event *event, int mode)
|
||||
{
|
||||
bool lost;
|
||||
int cpu = smp_processor_id();
|
||||
unsigned long size;
|
||||
struct coresight_device *sink, *csdev = per_cpu(csdev_src, cpu);
|
||||
struct perf_output_handle *handle = this_cpu_ptr(&ctx_handle);
|
||||
struct etm_event_data *event_data = perf_get_aux(handle);
|
||||
|
||||
if (event->hw.state == PERF_HES_STOPPED)
|
||||
return;
|
||||
|
||||
if (!csdev)
|
||||
return;
|
||||
|
||||
sink = coresight_get_sink(event_data->path[cpu]);
|
||||
if (!sink)
|
||||
return;
|
||||
|
||||
/* stop tracer */
|
||||
source_ops(csdev)->disable(csdev);
|
||||
|
||||
/* tell the core */
|
||||
event->hw.state = PERF_HES_STOPPED;
|
||||
|
||||
if (mode & PERF_EF_UPDATE) {
|
||||
if (WARN_ON_ONCE(handle->event != event))
|
||||
return;
|
||||
|
||||
/* update trace information */
|
||||
if (!sink_ops(sink)->update_buffer)
|
||||
return;
|
||||
|
||||
sink_ops(sink)->update_buffer(sink, handle,
|
||||
event_data->snk_config);
|
||||
|
||||
if (!sink_ops(sink)->reset_buffer)
|
||||
return;
|
||||
|
||||
size = sink_ops(sink)->reset_buffer(sink, handle,
|
||||
event_data->snk_config,
|
||||
&lost);
|
||||
|
||||
perf_aux_output_end(handle, size, lost);
|
||||
}
|
||||
|
||||
/* Disabling the path make its elements available to other sessions */
|
||||
coresight_disable_path(event_data->path[cpu]);
|
||||
}
|
||||
|
||||
static int etm_event_add(struct perf_event *event, int mode)
|
||||
{
|
||||
int ret = 0;
|
||||
struct hw_perf_event *hwc = &event->hw;
|
||||
|
||||
if (mode & PERF_EF_START) {
|
||||
etm_event_start(event, 0);
|
||||
if (hwc->state & PERF_HES_STOPPED)
|
||||
ret = -EINVAL;
|
||||
} else {
|
||||
hwc->state = PERF_HES_STOPPED;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void etm_event_del(struct perf_event *event, int mode)
|
||||
{
|
||||
etm_event_stop(event, PERF_EF_UPDATE);
|
||||
}
|
||||
|
||||
int etm_perf_symlink(struct coresight_device *csdev, bool link)
|
||||
{
|
||||
char entry[sizeof("cpu9999999")];
|
||||
int ret = 0, cpu = source_ops(csdev)->cpu_id(csdev);
|
||||
struct device *pmu_dev = etm_pmu.dev;
|
||||
struct device *cs_dev = &csdev->dev;
|
||||
|
||||
sprintf(entry, "cpu%d", cpu);
|
||||
|
||||
if (!etm_perf_up)
|
||||
return -EPROBE_DEFER;
|
||||
|
||||
if (link) {
|
||||
ret = sysfs_create_link(&pmu_dev->kobj, &cs_dev->kobj, entry);
|
||||
if (ret)
|
||||
return ret;
|
||||
per_cpu(csdev_src, cpu) = csdev;
|
||||
} else {
|
||||
sysfs_remove_link(&pmu_dev->kobj, entry);
|
||||
per_cpu(csdev_src, cpu) = NULL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init etm_perf_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
etm_pmu.capabilities = PERF_PMU_CAP_EXCLUSIVE;
|
||||
|
||||
etm_pmu.attr_groups = etm_pmu_attr_groups;
|
||||
etm_pmu.task_ctx_nr = perf_sw_context;
|
||||
etm_pmu.read = etm_event_read;
|
||||
etm_pmu.event_init = etm_event_init;
|
||||
etm_pmu.setup_aux = etm_setup_aux;
|
||||
etm_pmu.free_aux = etm_free_aux;
|
||||
etm_pmu.start = etm_event_start;
|
||||
etm_pmu.stop = etm_event_stop;
|
||||
etm_pmu.add = etm_event_add;
|
||||
etm_pmu.del = etm_event_del;
|
||||
|
||||
ret = perf_pmu_register(&etm_pmu, CORESIGHT_ETM_PMU_NAME, -1);
|
||||
if (ret == 0)
|
||||
etm_perf_up = true;
|
||||
|
||||
return ret;
|
||||
}
|
||||
device_initcall(etm_perf_init);
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright(C) 2015 Linaro Limited. All rights reserved.
|
||||
* Author: Mathieu Poirier <mathieu.poirier@linaro.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _CORESIGHT_ETM_PERF_H
|
||||
#define _CORESIGHT_ETM_PERF_H
|
||||
|
||||
struct coresight_device;
|
||||
|
||||
#ifdef CONFIG_CORESIGHT
|
||||
int etm_perf_symlink(struct coresight_device *csdev, bool link);
|
||||
|
||||
#else
|
||||
static inline int etm_perf_symlink(struct coresight_device *csdev, bool link)
|
||||
{ return -EINVAL; }
|
||||
|
||||
#endif /* CONFIG_CORESIGHT */
|
||||
|
||||
#endif
|
|
@ -13,6 +13,7 @@
|
|||
#ifndef _CORESIGHT_CORESIGHT_ETM_H
|
||||
#define _CORESIGHT_CORESIGHT_ETM_H
|
||||
|
||||
#include <asm/local.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include "coresight-priv.h"
|
||||
|
||||
|
@ -109,7 +110,10 @@
|
|||
#define ETM_MODE_STALL BIT(2)
|
||||
#define ETM_MODE_TIMESTAMP BIT(3)
|
||||
#define ETM_MODE_CTXID BIT(4)
|
||||
#define ETM_MODE_ALL 0x1f
|
||||
#define ETM_MODE_ALL (ETM_MODE_EXCLUDE | ETM_MODE_CYCACC | \
|
||||
ETM_MODE_STALL | ETM_MODE_TIMESTAMP | \
|
||||
ETM_MODE_CTXID | ETM_MODE_EXCL_KERN | \
|
||||
ETM_MODE_EXCL_USER)
|
||||
|
||||
#define ETM_SQR_MASK 0x3
|
||||
#define ETM_TRACEID_MASK 0x3f
|
||||
|
@ -136,35 +140,16 @@
|
|||
#define ETM_DEFAULT_EVENT_VAL (ETM_HARD_WIRE_RES_A | \
|
||||
ETM_ADD_COMP_0 | \
|
||||
ETM_EVENT_NOT_A)
|
||||
|
||||
/**
|
||||
* struct etm_drvdata - specifics associated to an ETM component
|
||||
* @base: memory mapped base address for this component.
|
||||
* @dev: the device entity associated to this component.
|
||||
* @atclk: optional clock for the core parts of the ETM.
|
||||
* @csdev: component vitals needed by the framework.
|
||||
* @spinlock: only one at a time pls.
|
||||
* @cpu: the cpu this component is affined to.
|
||||
* @port_size: port size as reported by ETMCR bit 4-6 and 21.
|
||||
* @arch: ETM/PTM version number.
|
||||
* @use_cpu14: true if management registers need to be accessed via CP14.
|
||||
* @enable: is this ETM/PTM currently tracing.
|
||||
* @sticky_enable: true if ETM base configuration has been done.
|
||||
* @boot_enable:true if we should start tracing at boot time.
|
||||
* @os_unlock: true if access to management registers is allowed.
|
||||
* @nr_addr_cmp:Number of pairs of address comparators as found in ETMCCR.
|
||||
* @nr_cntr: Number of counters as found in ETMCCR bit 13-15.
|
||||
* @nr_ext_inp: Number of external input as found in ETMCCR bit 17-19.
|
||||
* @nr_ext_out: Number of external output as found in ETMCCR bit 20-22.
|
||||
* @nr_ctxid_cmp: Number of contextID comparators as found in ETMCCR bit 24-25.
|
||||
* @etmccr: value of register ETMCCR.
|
||||
* @etmccer: value of register ETMCCER.
|
||||
* @traceid: value of the current ID for this component.
|
||||
* struct etm_config - configuration information related to an ETM
|
||||
* @mode: controls various modes supported by this ETM/PTM.
|
||||
* @ctrl: used in conjunction with @mode.
|
||||
* @trigger_event: setting for register ETMTRIGGER.
|
||||
* @startstop_ctrl: setting for register ETMTSSCR.
|
||||
* @enable_event: setting for register ETMTEEVR.
|
||||
* @enable_ctrl1: setting for register ETMTECR1.
|
||||
* @enable_ctrl2: setting for register ETMTECR2.
|
||||
* @fifofull_level: setting for register ETMFFLR.
|
||||
* @addr_idx: index for the address comparator selection.
|
||||
* @addr_val: value for address comparator register.
|
||||
|
@ -189,36 +174,16 @@
|
|||
* @ctxid_mask: mask applicable to all the context IDs.
|
||||
* @sync_freq: Synchronisation frequency.
|
||||
* @timestamp_event: Defines an event that requests the insertion
|
||||
of a timestamp into the trace stream.
|
||||
* of a timestamp into the trace stream.
|
||||
*/
|
||||
struct etm_drvdata {
|
||||
void __iomem *base;
|
||||
struct device *dev;
|
||||
struct clk *atclk;
|
||||
struct coresight_device *csdev;
|
||||
spinlock_t spinlock;
|
||||
int cpu;
|
||||
int port_size;
|
||||
u8 arch;
|
||||
bool use_cp14;
|
||||
bool enable;
|
||||
bool sticky_enable;
|
||||
bool boot_enable;
|
||||
bool os_unlock;
|
||||
u8 nr_addr_cmp;
|
||||
u8 nr_cntr;
|
||||
u8 nr_ext_inp;
|
||||
u8 nr_ext_out;
|
||||
u8 nr_ctxid_cmp;
|
||||
u32 etmccr;
|
||||
u32 etmccer;
|
||||
u32 traceid;
|
||||
struct etm_config {
|
||||
u32 mode;
|
||||
u32 ctrl;
|
||||
u32 trigger_event;
|
||||
u32 startstop_ctrl;
|
||||
u32 enable_event;
|
||||
u32 enable_ctrl1;
|
||||
u32 enable_ctrl2;
|
||||
u32 fifofull_level;
|
||||
u8 addr_idx;
|
||||
u32 addr_val[ETM_MAX_ADDR_CMP];
|
||||
|
@ -244,6 +209,56 @@ struct etm_drvdata {
|
|||
u32 timestamp_event;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct etm_drvdata - specifics associated to an ETM component
|
||||
* @base: memory mapped base address for this component.
|
||||
* @dev: the device entity associated to this component.
|
||||
* @atclk: optional clock for the core parts of the ETM.
|
||||
* @csdev: component vitals needed by the framework.
|
||||
* @spinlock: only one at a time pls.
|
||||
* @cpu: the cpu this component is affined to.
|
||||
* @port_size: port size as reported by ETMCR bit 4-6 and 21.
|
||||
* @arch: ETM/PTM version number.
|
||||
* @use_cpu14: true if management registers need to be accessed via CP14.
|
||||
* @mode: this tracer's mode, i.e sysFS, Perf or disabled.
|
||||
* @sticky_enable: true if ETM base configuration has been done.
|
||||
* @boot_enable:true if we should start tracing at boot time.
|
||||
* @os_unlock: true if access to management registers is allowed.
|
||||
* @nr_addr_cmp:Number of pairs of address comparators as found in ETMCCR.
|
||||
* @nr_cntr: Number of counters as found in ETMCCR bit 13-15.
|
||||
* @nr_ext_inp: Number of external input as found in ETMCCR bit 17-19.
|
||||
* @nr_ext_out: Number of external output as found in ETMCCR bit 20-22.
|
||||
* @nr_ctxid_cmp: Number of contextID comparators as found in ETMCCR bit 24-25.
|
||||
* @etmccr: value of register ETMCCR.
|
||||
* @etmccer: value of register ETMCCER.
|
||||
* @traceid: value of the current ID for this component.
|
||||
* @config: structure holding configuration parameters.
|
||||
*/
|
||||
struct etm_drvdata {
|
||||
void __iomem *base;
|
||||
struct device *dev;
|
||||
struct clk *atclk;
|
||||
struct coresight_device *csdev;
|
||||
spinlock_t spinlock;
|
||||
int cpu;
|
||||
int port_size;
|
||||
u8 arch;
|
||||
bool use_cp14;
|
||||
local_t mode;
|
||||
bool sticky_enable;
|
||||
bool boot_enable;
|
||||
bool os_unlock;
|
||||
u8 nr_addr_cmp;
|
||||
u8 nr_cntr;
|
||||
u8 nr_ext_inp;
|
||||
u8 nr_ext_out;
|
||||
u8 nr_ctxid_cmp;
|
||||
u32 etmccr;
|
||||
u32 etmccer;
|
||||
u32 traceid;
|
||||
struct etm_config config;
|
||||
};
|
||||
|
||||
enum etm_addr_type {
|
||||
ETM_ADDR_TYPE_NONE,
|
||||
ETM_ADDR_TYPE_SINGLE,
|
||||
|
@ -251,4 +266,39 @@ enum etm_addr_type {
|
|||
ETM_ADDR_TYPE_START,
|
||||
ETM_ADDR_TYPE_STOP,
|
||||
};
|
||||
|
||||
static inline void etm_writel(struct etm_drvdata *drvdata,
|
||||
u32 val, u32 off)
|
||||
{
|
||||
if (drvdata->use_cp14) {
|
||||
if (etm_writel_cp14(off, val)) {
|
||||
dev_err(drvdata->dev,
|
||||
"invalid CP14 access to ETM reg: %#x", off);
|
||||
}
|
||||
} else {
|
||||
writel_relaxed(val, drvdata->base + off);
|
||||
}
|
||||
}
|
||||
|
||||
static inline unsigned int etm_readl(struct etm_drvdata *drvdata, u32 off)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
if (drvdata->use_cp14) {
|
||||
if (etm_readl_cp14(off, &val)) {
|
||||
dev_err(drvdata->dev,
|
||||
"invalid CP14 access to ETM reg: %#x", off);
|
||||
}
|
||||
} else {
|
||||
val = readl_relaxed(drvdata->base + off);
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
extern const struct attribute_group *coresight_etm_groups[];
|
||||
int etm_get_trace_id(struct etm_drvdata *drvdata);
|
||||
void etm_set_default(struct etm_config *config);
|
||||
void etm_config_trace_mode(struct etm_config *config);
|
||||
struct etm_config *get_etm_config(struct etm_drvdata *drvdata);
|
||||
#endif
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -15,7 +15,6 @@
|
|||
#include <linux/init.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/fs.h>
|
||||
|
@ -32,6 +31,7 @@
|
|||
#include <linux/seq_file.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/perf_event.h>
|
||||
#include <asm/sections.h>
|
||||
|
||||
#include "coresight-etm4x.h"
|
||||
|
@ -63,6 +63,13 @@ static bool etm4_arch_supported(u8 arch)
|
|||
return true;
|
||||
}
|
||||
|
||||
static int etm4_cpu_id(struct coresight_device *csdev)
|
||||
{
|
||||
struct etmv4_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
return drvdata->cpu;
|
||||
}
|
||||
|
||||
static int etm4_trace_id(struct coresight_device *csdev)
|
||||
{
|
||||
struct etmv4_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
@ -72,7 +79,6 @@ static int etm4_trace_id(struct coresight_device *csdev)
|
|||
if (!drvdata->enable)
|
||||
return drvdata->trcid;
|
||||
|
||||
pm_runtime_get_sync(drvdata->dev);
|
||||
spin_lock_irqsave(&drvdata->spinlock, flags);
|
||||
|
||||
CS_UNLOCK(drvdata->base);
|
||||
|
@ -81,7 +87,6 @@ static int etm4_trace_id(struct coresight_device *csdev)
|
|||
CS_LOCK(drvdata->base);
|
||||
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
pm_runtime_put(drvdata->dev);
|
||||
|
||||
return trace_id;
|
||||
}
|
||||
|
@ -182,12 +187,12 @@ static void etm4_enable_hw(void *info)
|
|||
dev_dbg(drvdata->dev, "cpu: %d enable smp call done\n", drvdata->cpu);
|
||||
}
|
||||
|
||||
static int etm4_enable(struct coresight_device *csdev)
|
||||
static int etm4_enable(struct coresight_device *csdev,
|
||||
struct perf_event_attr *attr, u32 mode)
|
||||
{
|
||||
struct etmv4_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
int ret;
|
||||
|
||||
pm_runtime_get_sync(drvdata->dev);
|
||||
spin_lock(&drvdata->spinlock);
|
||||
|
||||
/*
|
||||
|
@ -207,7 +212,6 @@ static int etm4_enable(struct coresight_device *csdev)
|
|||
return 0;
|
||||
err:
|
||||
spin_unlock(&drvdata->spinlock);
|
||||
pm_runtime_put(drvdata->dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -256,12 +260,11 @@ static void etm4_disable(struct coresight_device *csdev)
|
|||
spin_unlock(&drvdata->spinlock);
|
||||
put_online_cpus();
|
||||
|
||||
pm_runtime_put(drvdata->dev);
|
||||
|
||||
dev_info(drvdata->dev, "ETM tracing disabled\n");
|
||||
}
|
||||
|
||||
static const struct coresight_ops_source etm4_source_ops = {
|
||||
.cpu_id = etm4_cpu_id,
|
||||
.trace_id = etm4_trace_id,
|
||||
.enable = etm4_enable,
|
||||
.disable = etm4_disable,
|
||||
|
@ -2219,7 +2222,7 @@ static ssize_t name##_show(struct device *_dev, \
|
|||
return scnprintf(buf, PAGE_SIZE, "0x%x\n", \
|
||||
readl_relaxed(drvdata->base + offset)); \
|
||||
} \
|
||||
DEVICE_ATTR_RO(name)
|
||||
static DEVICE_ATTR_RO(name)
|
||||
|
||||
coresight_simple_func(trcoslsr, TRCOSLSR);
|
||||
coresight_simple_func(trcpdcr, TRCPDCR);
|
||||
|
@ -2684,17 +2687,6 @@ err_coresight_register:
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int etm4_remove(struct amba_device *adev)
|
||||
{
|
||||
struct etmv4_drvdata *drvdata = amba_get_drvdata(adev);
|
||||
|
||||
coresight_unregister(drvdata->csdev);
|
||||
if (--etm4_count == 0)
|
||||
unregister_hotcpu_notifier(&etm4_cpu_notifier);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct amba_id etm4_ids[] = {
|
||||
{ /* ETM 4.0 - Qualcomm */
|
||||
.id = 0x0003b95d,
|
||||
|
@ -2712,10 +2704,9 @@ static struct amba_id etm4_ids[] = {
|
|||
static struct amba_driver etm4x_driver = {
|
||||
.drv = {
|
||||
.name = "coresight-etm4x",
|
||||
.suppress_bind_attrs = true,
|
||||
},
|
||||
.probe = etm4_probe,
|
||||
.remove = etm4_remove,
|
||||
.id_table = etm4_ids,
|
||||
};
|
||||
|
||||
module_amba_driver(etm4x_driver);
|
||||
builtin_amba_driver(etm4x_driver);
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* Description: CoreSight Funnel 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 and
|
||||
|
@ -11,7 +13,6 @@
|
|||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/device.h>
|
||||
|
@ -69,7 +70,6 @@ static int funnel_enable(struct coresight_device *csdev, int inport,
|
|||
{
|
||||
struct funnel_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
pm_runtime_get_sync(drvdata->dev);
|
||||
funnel_enable_hw(drvdata, inport);
|
||||
|
||||
dev_info(drvdata->dev, "FUNNEL inport %d enabled\n", inport);
|
||||
|
@ -95,7 +95,6 @@ static void funnel_disable(struct coresight_device *csdev, int inport,
|
|||
struct funnel_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
funnel_disable_hw(drvdata, inport);
|
||||
pm_runtime_put(drvdata->dev);
|
||||
|
||||
dev_info(drvdata->dev, "FUNNEL inport %d disabled\n", inport);
|
||||
}
|
||||
|
@ -226,14 +225,6 @@ static int funnel_probe(struct amba_device *adev, const struct amba_id *id)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int funnel_remove(struct amba_device *adev)
|
||||
{
|
||||
struct funnel_drvdata *drvdata = amba_get_drvdata(adev);
|
||||
|
||||
coresight_unregister(drvdata->csdev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int funnel_runtime_suspend(struct device *dev)
|
||||
{
|
||||
|
@ -273,13 +264,9 @@ static struct amba_driver funnel_driver = {
|
|||
.name = "coresight-funnel",
|
||||
.owner = THIS_MODULE,
|
||||
.pm = &funnel_dev_pm_ops,
|
||||
.suppress_bind_attrs = true,
|
||||
},
|
||||
.probe = funnel_probe,
|
||||
.remove = funnel_remove,
|
||||
.id_table = funnel_ids,
|
||||
};
|
||||
|
||||
module_amba_driver(funnel_driver);
|
||||
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_DESCRIPTION("CoreSight Funnel driver");
|
||||
builtin_amba_driver(funnel_driver);
|
||||
|
|
|
@ -34,6 +34,15 @@
|
|||
#define TIMEOUT_US 100
|
||||
#define BMVAL(val, lsb, msb) ((val & GENMASK(msb, lsb)) >> lsb)
|
||||
|
||||
#define ETM_MODE_EXCL_KERN BIT(30)
|
||||
#define ETM_MODE_EXCL_USER BIT(31)
|
||||
|
||||
enum cs_mode {
|
||||
CS_MODE_DISABLED,
|
||||
CS_MODE_SYSFS,
|
||||
CS_MODE_PERF,
|
||||
};
|
||||
|
||||
static inline void CS_LOCK(void __iomem *addr)
|
||||
{
|
||||
do {
|
||||
|
@ -52,6 +61,12 @@ static inline void CS_UNLOCK(void __iomem *addr)
|
|||
} while (0);
|
||||
}
|
||||
|
||||
void coresight_disable_path(struct list_head *path);
|
||||
int coresight_enable_path(struct list_head *path, u32 mode);
|
||||
struct coresight_device *coresight_get_sink(struct list_head *path);
|
||||
struct list_head *coresight_build_path(struct coresight_device *csdev);
|
||||
void coresight_release_path(struct list_head *path);
|
||||
|
||||
#ifdef CONFIG_CORESIGHT_SOURCE_ETM3X
|
||||
extern int etm_readl_cp14(u32 off, unsigned int *val);
|
||||
extern int etm_writel_cp14(u32 off, u32 val);
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
#include <linux/clk.h>
|
||||
#include <linux/coresight.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/io.h>
|
||||
|
@ -48,8 +47,6 @@ static int replicator_enable(struct coresight_device *csdev, int inport,
|
|||
{
|
||||
struct replicator_state *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
pm_runtime_get_sync(drvdata->dev);
|
||||
|
||||
CS_UNLOCK(drvdata->base);
|
||||
|
||||
/*
|
||||
|
@ -86,8 +83,6 @@ static void replicator_disable(struct coresight_device *csdev, int inport,
|
|||
|
||||
CS_LOCK(drvdata->base);
|
||||
|
||||
pm_runtime_put(drvdata->dev);
|
||||
|
||||
dev_info(drvdata->dev, "REPLICATOR disabled\n");
|
||||
}
|
||||
|
||||
|
@ -156,15 +151,6 @@ static int replicator_probe(struct amba_device *adev, const struct amba_id *id)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int replicator_remove(struct amba_device *adev)
|
||||
{
|
||||
struct replicator_state *drvdata = amba_get_drvdata(adev);
|
||||
|
||||
pm_runtime_disable(&adev->dev);
|
||||
coresight_unregister(drvdata->csdev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int replicator_runtime_suspend(struct device *dev)
|
||||
{
|
||||
|
@ -206,10 +192,9 @@ static struct amba_driver replicator_driver = {
|
|||
.drv = {
|
||||
.name = "coresight-replicator-qcom",
|
||||
.pm = &replicator_dev_pm_ops,
|
||||
.suppress_bind_attrs = true,
|
||||
},
|
||||
.probe = replicator_probe,
|
||||
.remove = replicator_remove,
|
||||
.id_table = replicator_ids,
|
||||
};
|
||||
|
||||
module_amba_driver(replicator_driver);
|
||||
builtin_amba_driver(replicator_driver);
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* Description: CoreSight Replicator 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 and
|
||||
|
@ -11,7 +13,6 @@
|
|||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/io.h>
|
||||
|
@ -41,7 +42,6 @@ static int replicator_enable(struct coresight_device *csdev, int inport,
|
|||
{
|
||||
struct replicator_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
pm_runtime_get_sync(drvdata->dev);
|
||||
dev_info(drvdata->dev, "REPLICATOR enabled\n");
|
||||
return 0;
|
||||
}
|
||||
|
@ -51,7 +51,6 @@ static void replicator_disable(struct coresight_device *csdev, int inport,
|
|||
{
|
||||
struct replicator_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
pm_runtime_put(drvdata->dev);
|
||||
dev_info(drvdata->dev, "REPLICATOR disabled\n");
|
||||
}
|
||||
|
||||
|
@ -127,20 +126,6 @@ out_disable_pm:
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int replicator_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct replicator_drvdata *drvdata = platform_get_drvdata(pdev);
|
||||
|
||||
coresight_unregister(drvdata->csdev);
|
||||
pm_runtime_get_sync(&pdev->dev);
|
||||
if (!IS_ERR(drvdata->atclk))
|
||||
clk_disable_unprepare(drvdata->atclk);
|
||||
pm_runtime_put_noidle(&pdev->dev);
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int replicator_runtime_suspend(struct device *dev)
|
||||
{
|
||||
|
@ -175,15 +160,11 @@ static const struct of_device_id replicator_match[] = {
|
|||
|
||||
static struct platform_driver replicator_driver = {
|
||||
.probe = replicator_probe,
|
||||
.remove = replicator_remove,
|
||||
.driver = {
|
||||
.name = "coresight-replicator",
|
||||
.of_match_table = replicator_match,
|
||||
.pm = &replicator_dev_pm_ops,
|
||||
.suppress_bind_attrs = true,
|
||||
},
|
||||
};
|
||||
|
||||
builtin_platform_driver(replicator_driver);
|
||||
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_DESCRIPTION("CoreSight Replicator driver");
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
/* Copyright (c) 2012, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* Description: CoreSight Trace Memory Controller 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 and
|
||||
|
@ -11,7 +13,6 @@
|
|||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/device.h>
|
||||
|
@ -124,7 +125,7 @@ struct tmc_drvdata {
|
|||
bool reading;
|
||||
char *buf;
|
||||
dma_addr_t paddr;
|
||||
void __iomem *vaddr;
|
||||
void *vaddr;
|
||||
u32 size;
|
||||
bool enable;
|
||||
enum tmc_config_type config_type;
|
||||
|
@ -242,12 +243,9 @@ static int tmc_enable(struct tmc_drvdata *drvdata, enum tmc_mode mode)
|
|||
{
|
||||
unsigned long flags;
|
||||
|
||||
pm_runtime_get_sync(drvdata->dev);
|
||||
|
||||
spin_lock_irqsave(&drvdata->spinlock, flags);
|
||||
if (drvdata->reading) {
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
pm_runtime_put(drvdata->dev);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
|
@ -268,7 +266,7 @@ static int tmc_enable(struct tmc_drvdata *drvdata, enum tmc_mode mode)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int tmc_enable_sink(struct coresight_device *csdev)
|
||||
static int tmc_enable_sink(struct coresight_device *csdev, u32 mode)
|
||||
{
|
||||
struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
|
@ -381,8 +379,6 @@ out:
|
|||
drvdata->enable = false;
|
||||
spin_unlock_irqrestore(&drvdata->spinlock, flags);
|
||||
|
||||
pm_runtime_put(drvdata->dev);
|
||||
|
||||
dev_info(drvdata->dev, "TMC disabled\n");
|
||||
}
|
||||
|
||||
|
@ -766,23 +762,10 @@ err_misc_register:
|
|||
err_devm_kzalloc:
|
||||
if (drvdata->config_type == TMC_CONFIG_TYPE_ETR)
|
||||
dma_free_coherent(dev, drvdata->size,
|
||||
&drvdata->paddr, GFP_KERNEL);
|
||||
drvdata->vaddr, drvdata->paddr);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int tmc_remove(struct amba_device *adev)
|
||||
{
|
||||
struct tmc_drvdata *drvdata = amba_get_drvdata(adev);
|
||||
|
||||
misc_deregister(&drvdata->miscdev);
|
||||
coresight_unregister(drvdata->csdev);
|
||||
if (drvdata->config_type == TMC_CONFIG_TYPE_ETR)
|
||||
dma_free_coherent(drvdata->dev, drvdata->size,
|
||||
&drvdata->paddr, GFP_KERNEL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct amba_id tmc_ids[] = {
|
||||
{
|
||||
.id = 0x0003b961,
|
||||
|
@ -795,13 +778,9 @@ static struct amba_driver tmc_driver = {
|
|||
.drv = {
|
||||
.name = "coresight-tmc",
|
||||
.owner = THIS_MODULE,
|
||||
.suppress_bind_attrs = true,
|
||||
},
|
||||
.probe = tmc_probe,
|
||||
.remove = tmc_remove,
|
||||
.id_table = tmc_ids,
|
||||
};
|
||||
|
||||
module_amba_driver(tmc_driver);
|
||||
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_DESCRIPTION("CoreSight Trace Memory Controller driver");
|
||||
builtin_amba_driver(tmc_driver);
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* Description: CoreSight Trace Port Interface Unit 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 and
|
||||
|
@ -11,7 +13,6 @@
|
|||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/io.h>
|
||||
|
@ -70,11 +71,10 @@ static void tpiu_enable_hw(struct tpiu_drvdata *drvdata)
|
|||
CS_LOCK(drvdata->base);
|
||||
}
|
||||
|
||||
static int tpiu_enable(struct coresight_device *csdev)
|
||||
static int tpiu_enable(struct coresight_device *csdev, u32 mode)
|
||||
{
|
||||
struct tpiu_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
pm_runtime_get_sync(csdev->dev.parent);
|
||||
tpiu_enable_hw(drvdata);
|
||||
|
||||
dev_info(drvdata->dev, "TPIU enabled\n");
|
||||
|
@ -98,7 +98,6 @@ static void tpiu_disable(struct coresight_device *csdev)
|
|||
struct tpiu_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
|
||||
|
||||
tpiu_disable_hw(drvdata);
|
||||
pm_runtime_put(csdev->dev.parent);
|
||||
|
||||
dev_info(drvdata->dev, "TPIU disabled\n");
|
||||
}
|
||||
|
@ -172,14 +171,6 @@ static int tpiu_probe(struct amba_device *adev, const struct amba_id *id)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int tpiu_remove(struct amba_device *adev)
|
||||
{
|
||||
struct tpiu_drvdata *drvdata = amba_get_drvdata(adev);
|
||||
|
||||
coresight_unregister(drvdata->csdev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int tpiu_runtime_suspend(struct device *dev)
|
||||
{
|
||||
|
@ -223,13 +214,9 @@ static struct amba_driver tpiu_driver = {
|
|||
.name = "coresight-tpiu",
|
||||
.owner = THIS_MODULE,
|
||||
.pm = &tpiu_dev_pm_ops,
|
||||
.suppress_bind_attrs = true,
|
||||
},
|
||||
.probe = tpiu_probe,
|
||||
.remove = tpiu_remove,
|
||||
.id_table = tpiu_ids,
|
||||
};
|
||||
|
||||
module_amba_driver(tpiu_driver);
|
||||
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_DESCRIPTION("CoreSight Trace Port Interface Unit driver");
|
||||
builtin_amba_driver(tpiu_driver);
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/device.h>
|
||||
|
@ -24,11 +23,28 @@
|
|||
#include <linux/coresight.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
|
||||
#include "coresight-priv.h"
|
||||
|
||||
static DEFINE_MUTEX(coresight_mutex);
|
||||
|
||||
/**
|
||||
* struct coresight_node - elements of a path, from source to sink
|
||||
* @csdev: Address of an element.
|
||||
* @link: hook to the list.
|
||||
*/
|
||||
struct coresight_node {
|
||||
struct coresight_device *csdev;
|
||||
struct list_head link;
|
||||
};
|
||||
|
||||
/*
|
||||
* When operating Coresight drivers from the sysFS interface, only a single
|
||||
* path can exist from a tracer (associated to a CPU) to a sink.
|
||||
*/
|
||||
static DEFINE_PER_CPU(struct list_head *, sysfs_path);
|
||||
|
||||
static int coresight_id_match(struct device *dev, void *data)
|
||||
{
|
||||
int trace_id, i_trace_id;
|
||||
|
@ -68,15 +84,12 @@ static int coresight_source_is_unique(struct coresight_device *csdev)
|
|||
csdev, coresight_id_match);
|
||||
}
|
||||
|
||||
static int coresight_find_link_inport(struct coresight_device *csdev)
|
||||
static int coresight_find_link_inport(struct coresight_device *csdev,
|
||||
struct coresight_device *parent)
|
||||
{
|
||||
int i;
|
||||
struct coresight_device *parent;
|
||||
struct coresight_connection *conn;
|
||||
|
||||
parent = container_of(csdev->path_link.next,
|
||||
struct coresight_device, path_link);
|
||||
|
||||
for (i = 0; i < parent->nr_outport; i++) {
|
||||
conn = &parent->conns[i];
|
||||
if (conn->child_dev == csdev)
|
||||
|
@ -89,15 +102,12 @@ static int coresight_find_link_inport(struct coresight_device *csdev)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int coresight_find_link_outport(struct coresight_device *csdev)
|
||||
static int coresight_find_link_outport(struct coresight_device *csdev,
|
||||
struct coresight_device *child)
|
||||
{
|
||||
int i;
|
||||
struct coresight_device *child;
|
||||
struct coresight_connection *conn;
|
||||
|
||||
child = container_of(csdev->path_link.prev,
|
||||
struct coresight_device, path_link);
|
||||
|
||||
for (i = 0; i < csdev->nr_outport; i++) {
|
||||
conn = &csdev->conns[i];
|
||||
if (conn->child_dev == child)
|
||||
|
@ -110,13 +120,13 @@ static int coresight_find_link_outport(struct coresight_device *csdev)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int coresight_enable_sink(struct coresight_device *csdev)
|
||||
static int coresight_enable_sink(struct coresight_device *csdev, u32 mode)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!csdev->enable) {
|
||||
if (sink_ops(csdev)->enable) {
|
||||
ret = sink_ops(csdev)->enable(csdev);
|
||||
ret = sink_ops(csdev)->enable(csdev, mode);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
@ -138,14 +148,19 @@ static void coresight_disable_sink(struct coresight_device *csdev)
|
|||
}
|
||||
}
|
||||
|
||||
static int coresight_enable_link(struct coresight_device *csdev)
|
||||
static int coresight_enable_link(struct coresight_device *csdev,
|
||||
struct coresight_device *parent,
|
||||
struct coresight_device *child)
|
||||
{
|
||||
int ret;
|
||||
int link_subtype;
|
||||
int refport, inport, outport;
|
||||
|
||||
inport = coresight_find_link_inport(csdev);
|
||||
outport = coresight_find_link_outport(csdev);
|
||||
if (!parent || !child)
|
||||
return -EINVAL;
|
||||
|
||||
inport = coresight_find_link_inport(csdev, parent);
|
||||
outport = coresight_find_link_outport(csdev, child);
|
||||
link_subtype = csdev->subtype.link_subtype;
|
||||
|
||||
if (link_subtype == CORESIGHT_DEV_SUBTYPE_LINK_MERG)
|
||||
|
@ -168,14 +183,19 @@ static int coresight_enable_link(struct coresight_device *csdev)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void coresight_disable_link(struct coresight_device *csdev)
|
||||
static void coresight_disable_link(struct coresight_device *csdev,
|
||||
struct coresight_device *parent,
|
||||
struct coresight_device *child)
|
||||
{
|
||||
int i, nr_conns;
|
||||
int link_subtype;
|
||||
int refport, inport, outport;
|
||||
|
||||
inport = coresight_find_link_inport(csdev);
|
||||
outport = coresight_find_link_outport(csdev);
|
||||
if (!parent || !child)
|
||||
return;
|
||||
|
||||
inport = coresight_find_link_inport(csdev, parent);
|
||||
outport = coresight_find_link_outport(csdev, child);
|
||||
link_subtype = csdev->subtype.link_subtype;
|
||||
|
||||
if (link_subtype == CORESIGHT_DEV_SUBTYPE_LINK_MERG) {
|
||||
|
@ -201,7 +221,7 @@ static void coresight_disable_link(struct coresight_device *csdev)
|
|||
csdev->enable = false;
|
||||
}
|
||||
|
||||
static int coresight_enable_source(struct coresight_device *csdev)
|
||||
static int coresight_enable_source(struct coresight_device *csdev, u32 mode)
|
||||
{
|
||||
int ret;
|
||||
|
||||
|
@ -213,7 +233,7 @@ static int coresight_enable_source(struct coresight_device *csdev)
|
|||
|
||||
if (!csdev->enable) {
|
||||
if (source_ops(csdev)->enable) {
|
||||
ret = source_ops(csdev)->enable(csdev);
|
||||
ret = source_ops(csdev)->enable(csdev, NULL, mode);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
@ -235,109 +255,188 @@ static void coresight_disable_source(struct coresight_device *csdev)
|
|||
}
|
||||
}
|
||||
|
||||
static int coresight_enable_path(struct list_head *path)
|
||||
void coresight_disable_path(struct list_head *path)
|
||||
{
|
||||
struct coresight_node *nd;
|
||||
struct coresight_device *csdev, *parent, *child;
|
||||
|
||||
list_for_each_entry(nd, path, link) {
|
||||
csdev = nd->csdev;
|
||||
|
||||
switch (csdev->type) {
|
||||
case CORESIGHT_DEV_TYPE_SINK:
|
||||
case CORESIGHT_DEV_TYPE_LINKSINK:
|
||||
coresight_disable_sink(csdev);
|
||||
break;
|
||||
case CORESIGHT_DEV_TYPE_SOURCE:
|
||||
/* sources are disabled from either sysFS or Perf */
|
||||
break;
|
||||
case CORESIGHT_DEV_TYPE_LINK:
|
||||
parent = list_prev_entry(nd, link)->csdev;
|
||||
child = list_next_entry(nd, link)->csdev;
|
||||
coresight_disable_link(csdev, parent, child);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int coresight_enable_path(struct list_head *path, u32 mode)
|
||||
{
|
||||
|
||||
int ret = 0;
|
||||
struct coresight_device *cd;
|
||||
struct coresight_node *nd;
|
||||
struct coresight_device *csdev, *parent, *child;
|
||||
|
||||
/*
|
||||
* At this point we have a full @path, from source to sink. The
|
||||
* sink is the first entry and the source the last one. Go through
|
||||
* all the components and enable them one by one.
|
||||
*/
|
||||
list_for_each_entry(cd, path, path_link) {
|
||||
if (cd == list_first_entry(path, struct coresight_device,
|
||||
path_link)) {
|
||||
ret = coresight_enable_sink(cd);
|
||||
} else if (list_is_last(&cd->path_link, path)) {
|
||||
/*
|
||||
* Don't enable the source just yet - this needs to
|
||||
* happen at the very end when all links and sink
|
||||
* along the path have been configured properly.
|
||||
*/
|
||||
;
|
||||
} else {
|
||||
ret = coresight_enable_link(cd);
|
||||
}
|
||||
if (ret)
|
||||
list_for_each_entry_reverse(nd, path, link) {
|
||||
csdev = nd->csdev;
|
||||
|
||||
switch (csdev->type) {
|
||||
case CORESIGHT_DEV_TYPE_SINK:
|
||||
case CORESIGHT_DEV_TYPE_LINKSINK:
|
||||
ret = coresight_enable_sink(csdev, mode);
|
||||
if (ret)
|
||||
goto err;
|
||||
break;
|
||||
case CORESIGHT_DEV_TYPE_SOURCE:
|
||||
/* sources are enabled from either sysFS or Perf */
|
||||
break;
|
||||
case CORESIGHT_DEV_TYPE_LINK:
|
||||
parent = list_prev_entry(nd, link)->csdev;
|
||||
child = list_next_entry(nd, link)->csdev;
|
||||
ret = coresight_enable_link(csdev, parent, child);
|
||||
if (ret)
|
||||
goto err;
|
||||
break;
|
||||
default:
|
||||
goto err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
err:
|
||||
list_for_each_entry_continue_reverse(cd, path, path_link) {
|
||||
if (cd == list_first_entry(path, struct coresight_device,
|
||||
path_link)) {
|
||||
coresight_disable_sink(cd);
|
||||
} else if (list_is_last(&cd->path_link, path)) {
|
||||
;
|
||||
} else {
|
||||
coresight_disable_link(cd);
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
return ret;
|
||||
err:
|
||||
coresight_disable_path(path);
|
||||
goto out;
|
||||
}
|
||||
|
||||
static int coresight_disable_path(struct list_head *path)
|
||||
struct coresight_device *coresight_get_sink(struct list_head *path)
|
||||
{
|
||||
struct coresight_device *cd;
|
||||
struct coresight_device *csdev;
|
||||
|
||||
list_for_each_entry_reverse(cd, path, path_link) {
|
||||
if (cd == list_first_entry(path, struct coresight_device,
|
||||
path_link)) {
|
||||
coresight_disable_sink(cd);
|
||||
} else if (list_is_last(&cd->path_link, path)) {
|
||||
/*
|
||||
* The source has already been stopped, no need
|
||||
* to do it again here.
|
||||
*/
|
||||
;
|
||||
} else {
|
||||
coresight_disable_link(cd);
|
||||
}
|
||||
}
|
||||
if (!path)
|
||||
return NULL;
|
||||
|
||||
return 0;
|
||||
csdev = list_last_entry(path, struct coresight_node, link)->csdev;
|
||||
if (csdev->type != CORESIGHT_DEV_TYPE_SINK &&
|
||||
csdev->type != CORESIGHT_DEV_TYPE_LINKSINK)
|
||||
return NULL;
|
||||
|
||||
return csdev;
|
||||
}
|
||||
|
||||
static int coresight_build_paths(struct coresight_device *csdev,
|
||||
struct list_head *path,
|
||||
bool enable)
|
||||
/**
|
||||
* _coresight_build_path - recursively build a path from a @csdev to a sink.
|
||||
* @csdev: The device to start from.
|
||||
* @path: The list to add devices to.
|
||||
*
|
||||
* The tree of Coresight device is traversed until an activated sink is
|
||||
* found. From there the sink is added to the list along with all the
|
||||
* devices that led to that point - the end result is a list from source
|
||||
* to sink. In that list the source is the first device and the sink the
|
||||
* last one.
|
||||
*/
|
||||
static int _coresight_build_path(struct coresight_device *csdev,
|
||||
struct list_head *path)
|
||||
{
|
||||
int i, ret = -EINVAL;
|
||||
int i;
|
||||
bool found = false;
|
||||
struct coresight_node *node;
|
||||
struct coresight_connection *conn;
|
||||
|
||||
list_add(&csdev->path_link, path);
|
||||
|
||||
/* An activated sink has been found. Enqueue the element */
|
||||
if ((csdev->type == CORESIGHT_DEV_TYPE_SINK ||
|
||||
csdev->type == CORESIGHT_DEV_TYPE_LINKSINK) &&
|
||||
csdev->activated) {
|
||||
if (enable)
|
||||
ret = coresight_enable_path(path);
|
||||
else
|
||||
ret = coresight_disable_path(path);
|
||||
} else {
|
||||
for (i = 0; i < csdev->nr_outport; i++) {
|
||||
conn = &csdev->conns[i];
|
||||
if (coresight_build_paths(conn->child_dev,
|
||||
path, enable) == 0)
|
||||
ret = 0;
|
||||
csdev->type == CORESIGHT_DEV_TYPE_LINKSINK) && csdev->activated)
|
||||
goto out;
|
||||
|
||||
/* Not a sink - recursively explore each port found on this element */
|
||||
for (i = 0; i < csdev->nr_outport; i++) {
|
||||
conn = &csdev->conns[i];
|
||||
if (_coresight_build_path(conn->child_dev, path) == 0) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (list_first_entry(path, struct coresight_device, path_link) != csdev)
|
||||
dev_err(&csdev->dev, "wrong device in %s\n", __func__);
|
||||
if (!found)
|
||||
return -ENODEV;
|
||||
|
||||
list_del(&csdev->path_link);
|
||||
out:
|
||||
/*
|
||||
* A path from this element to a sink has been found. The elements
|
||||
* leading to the sink are already enqueued, all that is left to do
|
||||
* is tell the PM runtime core we need this element and add a node
|
||||
* for it.
|
||||
*/
|
||||
node = kzalloc(sizeof(struct coresight_node), GFP_KERNEL);
|
||||
if (!node)
|
||||
return -ENOMEM;
|
||||
|
||||
return ret;
|
||||
node->csdev = csdev;
|
||||
list_add(&node->link, path);
|
||||
pm_runtime_get_sync(csdev->dev.parent);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct list_head *coresight_build_path(struct coresight_device *csdev)
|
||||
{
|
||||
struct list_head *path;
|
||||
|
||||
path = kzalloc(sizeof(struct list_head), GFP_KERNEL);
|
||||
if (!path)
|
||||
return NULL;
|
||||
|
||||
INIT_LIST_HEAD(path);
|
||||
|
||||
if (_coresight_build_path(csdev, path)) {
|
||||
kfree(path);
|
||||
path = NULL;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* coresight_release_path - release a previously built path.
|
||||
* @path: the path to release.
|
||||
*
|
||||
* Go through all the elements of a path and 1) removed it from the list and
|
||||
* 2) free the memory allocated for each node.
|
||||
*/
|
||||
void coresight_release_path(struct list_head *path)
|
||||
{
|
||||
struct coresight_device *csdev;
|
||||
struct coresight_node *nd, *next;
|
||||
|
||||
list_for_each_entry_safe(nd, next, path, link) {
|
||||
csdev = nd->csdev;
|
||||
|
||||
pm_runtime_put_sync(csdev->dev.parent);
|
||||
list_del(&nd->link);
|
||||
kfree(nd);
|
||||
}
|
||||
|
||||
kfree(path);
|
||||
path = NULL;
|
||||
}
|
||||
|
||||
int coresight_enable(struct coresight_device *csdev)
|
||||
{
|
||||
int ret = 0;
|
||||
LIST_HEAD(path);
|
||||
int cpu;
|
||||
struct list_head *path;
|
||||
|
||||
mutex_lock(&coresight_mutex);
|
||||
if (csdev->type != CORESIGHT_DEV_TYPE_SOURCE) {
|
||||
|
@ -348,22 +447,47 @@ int coresight_enable(struct coresight_device *csdev)
|
|||
if (csdev->enable)
|
||||
goto out;
|
||||
|
||||
if (coresight_build_paths(csdev, &path, true)) {
|
||||
dev_err(&csdev->dev, "building path(s) failed\n");
|
||||
path = coresight_build_path(csdev);
|
||||
if (!path) {
|
||||
pr_err("building path(s) failed\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (coresight_enable_source(csdev))
|
||||
dev_err(&csdev->dev, "source enable failed\n");
|
||||
ret = coresight_enable_path(path, CS_MODE_SYSFS);
|
||||
if (ret)
|
||||
goto err_path;
|
||||
|
||||
ret = coresight_enable_source(csdev, CS_MODE_SYSFS);
|
||||
if (ret)
|
||||
goto err_source;
|
||||
|
||||
/*
|
||||
* When working from sysFS it is important to keep track
|
||||
* of the paths that were created so that they can be
|
||||
* undone in 'coresight_disable()'. Since there can only
|
||||
* be a single session per tracer (when working from sysFS)
|
||||
* a per-cpu variable will do just fine.
|
||||
*/
|
||||
cpu = source_ops(csdev)->cpu_id(csdev);
|
||||
per_cpu(sysfs_path, cpu) = path;
|
||||
|
||||
out:
|
||||
mutex_unlock(&coresight_mutex);
|
||||
return ret;
|
||||
|
||||
err_source:
|
||||
coresight_disable_path(path);
|
||||
|
||||
err_path:
|
||||
coresight_release_path(path);
|
||||
goto out;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(coresight_enable);
|
||||
|
||||
void coresight_disable(struct coresight_device *csdev)
|
||||
{
|
||||
LIST_HEAD(path);
|
||||
int cpu;
|
||||
struct list_head *path;
|
||||
|
||||
mutex_lock(&coresight_mutex);
|
||||
if (csdev->type != CORESIGHT_DEV_TYPE_SOURCE) {
|
||||
|
@ -373,9 +497,12 @@ void coresight_disable(struct coresight_device *csdev)
|
|||
if (!csdev->enable)
|
||||
goto out;
|
||||
|
||||
cpu = source_ops(csdev)->cpu_id(csdev);
|
||||
path = per_cpu(sysfs_path, cpu);
|
||||
coresight_disable_source(csdev);
|
||||
if (coresight_build_paths(csdev, &path, false))
|
||||
dev_err(&csdev->dev, "releasing path(s) failed\n");
|
||||
coresight_disable_path(path);
|
||||
coresight_release_path(path);
|
||||
per_cpu(sysfs_path, cpu) = NULL;
|
||||
|
||||
out:
|
||||
mutex_unlock(&coresight_mutex);
|
||||
|
@ -481,6 +608,8 @@ static void coresight_device_release(struct device *dev)
|
|||
{
|
||||
struct coresight_device *csdev = to_coresight_device(dev);
|
||||
|
||||
kfree(csdev->conns);
|
||||
kfree(csdev->refcnt);
|
||||
kfree(csdev);
|
||||
}
|
||||
|
||||
|
@ -536,7 +665,7 @@ static void coresight_fixup_orphan_conns(struct coresight_device *csdev)
|
|||
* are hooked-up with each newly added component.
|
||||
*/
|
||||
bus_for_each_dev(&coresight_bustype, NULL,
|
||||
csdev, coresight_orphan_match);
|
||||
csdev, coresight_orphan_match);
|
||||
}
|
||||
|
||||
|
||||
|
@ -568,6 +697,8 @@ static void coresight_fixup_device_conns(struct coresight_device *csdev)
|
|||
|
||||
if (dev) {
|
||||
conn->child_dev = to_coresight_device(dev);
|
||||
/* and put reference from 'bus_find_device()' */
|
||||
put_device(dev);
|
||||
} else {
|
||||
csdev->orphan = true;
|
||||
conn->child_dev = NULL;
|
||||
|
@ -575,6 +706,50 @@ static void coresight_fixup_device_conns(struct coresight_device *csdev)
|
|||
}
|
||||
}
|
||||
|
||||
static int coresight_remove_match(struct device *dev, void *data)
|
||||
{
|
||||
int i;
|
||||
struct coresight_device *csdev, *iterator;
|
||||
struct coresight_connection *conn;
|
||||
|
||||
csdev = data;
|
||||
iterator = to_coresight_device(dev);
|
||||
|
||||
/* No need to check oneself */
|
||||
if (csdev == iterator)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* Circle throuch all the connection of that component. If we find
|
||||
* a connection whose name matches @csdev, remove it.
|
||||
*/
|
||||
for (i = 0; i < iterator->nr_outport; i++) {
|
||||
conn = &iterator->conns[i];
|
||||
|
||||
if (conn->child_dev == NULL)
|
||||
continue;
|
||||
|
||||
if (!strcmp(dev_name(&csdev->dev), conn->child_name)) {
|
||||
iterator->orphan = true;
|
||||
conn->child_dev = NULL;
|
||||
/* No need to continue */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Returning '0' ensures that all known component on the
|
||||
* bus will be checked.
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void coresight_remove_conns(struct coresight_device *csdev)
|
||||
{
|
||||
bus_for_each_dev(&coresight_bustype, NULL,
|
||||
csdev, coresight_remove_match);
|
||||
}
|
||||
|
||||
/**
|
||||
* coresight_timeout - loop until a bit has changed to a specific state.
|
||||
* @addr: base address of the area of interest.
|
||||
|
@ -713,13 +888,8 @@ EXPORT_SYMBOL_GPL(coresight_register);
|
|||
|
||||
void coresight_unregister(struct coresight_device *csdev)
|
||||
{
|
||||
mutex_lock(&coresight_mutex);
|
||||
|
||||
kfree(csdev->conns);
|
||||
/* Remove references of that device in the topology */
|
||||
coresight_remove_conns(csdev);
|
||||
device_unregister(&csdev->dev);
|
||||
|
||||
mutex_unlock(&coresight_mutex);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(coresight_unregister);
|
||||
|
||||
MODULE_LICENSE("GPL v2");
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/slab.h>
|
||||
|
@ -86,7 +85,7 @@ static int of_coresight_alloc_memory(struct device *dev,
|
|||
return -ENOMEM;
|
||||
|
||||
/* Children connected to this component via @outports */
|
||||
pdata->child_names = devm_kzalloc(dev, pdata->nr_outport *
|
||||
pdata->child_names = devm_kzalloc(dev, pdata->nr_outport *
|
||||
sizeof(*pdata->child_names),
|
||||
GFP_KERNEL);
|
||||
if (!pdata->child_names)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
config INTEL_TH
|
||||
tristate "Intel(R) Trace Hub controller"
|
||||
depends on HAS_DMA && HAS_IOMEM
|
||||
help
|
||||
Intel(R) Trace Hub (TH) is a set of hardware blocks (subdevices) that
|
||||
produce, switch and output trace data from multiple hardware and
|
||||
|
|
|
@ -124,17 +124,34 @@ static struct device_type intel_th_source_device_type = {
|
|||
.release = intel_th_device_release,
|
||||
};
|
||||
|
||||
static struct intel_th *to_intel_th(struct intel_th_device *thdev)
|
||||
{
|
||||
/*
|
||||
* subdevice tree is flat: if this one is not a switch, its
|
||||
* parent must be
|
||||
*/
|
||||
if (thdev->type != INTEL_TH_SWITCH)
|
||||
thdev = to_intel_th_hub(thdev);
|
||||
|
||||
if (WARN_ON_ONCE(!thdev || thdev->type != INTEL_TH_SWITCH))
|
||||
return NULL;
|
||||
|
||||
return dev_get_drvdata(thdev->dev.parent);
|
||||
}
|
||||
|
||||
static char *intel_th_output_devnode(struct device *dev, umode_t *mode,
|
||||
kuid_t *uid, kgid_t *gid)
|
||||
{
|
||||
struct intel_th_device *thdev = to_intel_th_device(dev);
|
||||
struct intel_th *th = to_intel_th(thdev);
|
||||
char *node;
|
||||
|
||||
if (thdev->id >= 0)
|
||||
node = kasprintf(GFP_KERNEL, "intel_th%d/%s%d", 0, thdev->name,
|
||||
thdev->id);
|
||||
node = kasprintf(GFP_KERNEL, "intel_th%d/%s%d", th->id,
|
||||
thdev->name, thdev->id);
|
||||
else
|
||||
node = kasprintf(GFP_KERNEL, "intel_th%d/%s", 0, thdev->name);
|
||||
node = kasprintf(GFP_KERNEL, "intel_th%d/%s", th->id,
|
||||
thdev->name);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
@ -319,6 +336,7 @@ static struct intel_th_subdevice {
|
|||
unsigned nres;
|
||||
unsigned type;
|
||||
unsigned otype;
|
||||
unsigned scrpd;
|
||||
int id;
|
||||
} intel_th_subdevices[TH_SUBDEVICE_MAX] = {
|
||||
{
|
||||
|
@ -352,6 +370,7 @@ static struct intel_th_subdevice {
|
|||
.id = 0,
|
||||
.type = INTEL_TH_OUTPUT,
|
||||
.otype = GTH_MSU,
|
||||
.scrpd = SCRPD_MEM_IS_PRIM_DEST | SCRPD_MSC0_IS_ENABLED,
|
||||
},
|
||||
{
|
||||
.nres = 2,
|
||||
|
@ -371,6 +390,7 @@ static struct intel_th_subdevice {
|
|||
.id = 1,
|
||||
.type = INTEL_TH_OUTPUT,
|
||||
.otype = GTH_MSU,
|
||||
.scrpd = SCRPD_MEM_IS_PRIM_DEST | SCRPD_MSC1_IS_ENABLED,
|
||||
},
|
||||
{
|
||||
.nres = 2,
|
||||
|
@ -403,6 +423,7 @@ static struct intel_th_subdevice {
|
|||
.name = "pti",
|
||||
.type = INTEL_TH_OUTPUT,
|
||||
.otype = GTH_PTI,
|
||||
.scrpd = SCRPD_PTI_IS_PRIM_DEST,
|
||||
},
|
||||
{
|
||||
.nres = 1,
|
||||
|
@ -477,6 +498,7 @@ static int intel_th_populate(struct intel_th *th, struct resource *devres,
|
|||
thdev->dev.devt = MKDEV(th->major, i);
|
||||
thdev->output.type = subdev->otype;
|
||||
thdev->output.port = -1;
|
||||
thdev->output.scratchpad = subdev->scrpd;
|
||||
}
|
||||
|
||||
err = device_add(&thdev->dev);
|
||||
|
@ -579,6 +601,8 @@ intel_th_alloc(struct device *dev, struct resource *devres,
|
|||
}
|
||||
th->dev = dev;
|
||||
|
||||
dev_set_drvdata(dev, th);
|
||||
|
||||
err = intel_th_populate(th, devres, ndevres, irq);
|
||||
if (err)
|
||||
goto err_chrdev;
|
||||
|
|
|
@ -146,24 +146,6 @@ gth_master_set(struct gth_device *gth, unsigned int master, int port)
|
|||
iowrite32(val, gth->base + reg);
|
||||
}
|
||||
|
||||
/*static int gth_master_get(struct gth_device *gth, unsigned int master)
|
||||
{
|
||||
unsigned int reg = REG_GTH_SWDEST0 + ((master >> 1) & ~3u);
|
||||
unsigned int shift = (master & 0x7) * 4;
|
||||
u32 val;
|
||||
|
||||
if (master >= 256) {
|
||||
reg = REG_GTH_GSWTDEST;
|
||||
shift = 0;
|
||||
}
|
||||
|
||||
val = ioread32(gth->base + reg);
|
||||
val &= (0xf << shift);
|
||||
val >>= shift;
|
||||
|
||||
return val ? val & 0x7 : -1;
|
||||
}*/
|
||||
|
||||
static ssize_t master_attr_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
|
@ -304,6 +286,10 @@ static int intel_th_gth_reset(struct gth_device *gth)
|
|||
if (scratchpad & SCRPD_DEBUGGER_IN_USE)
|
||||
return -EBUSY;
|
||||
|
||||
/* Always save/restore STH and TU registers in S0ix entry/exit */
|
||||
scratchpad |= SCRPD_STH_IS_ENABLED | SCRPD_TRIGGER_IS_ENABLED;
|
||||
iowrite32(scratchpad, gth->base + REG_GTH_SCRPD0);
|
||||
|
||||
/* output ports */
|
||||
for (port = 0; port < 8; port++) {
|
||||
if (gth_output_parm_get(gth, port, TH_OUTPUT_PARM(port)) ==
|
||||
|
@ -506,6 +492,10 @@ static void intel_th_gth_disable(struct intel_th_device *thdev,
|
|||
if (!count)
|
||||
dev_dbg(&thdev->dev, "timeout waiting for GTH[%d] PLE\n",
|
||||
output->port);
|
||||
|
||||
reg = ioread32(gth->base + REG_GTH_SCRPD0);
|
||||
reg &= ~output->scratchpad;
|
||||
iowrite32(reg, gth->base + REG_GTH_SCRPD0);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -520,7 +510,7 @@ static void intel_th_gth_enable(struct intel_th_device *thdev,
|
|||
struct intel_th_output *output)
|
||||
{
|
||||
struct gth_device *gth = dev_get_drvdata(&thdev->dev);
|
||||
u32 scr = 0xfc0000;
|
||||
u32 scr = 0xfc0000, scrpd;
|
||||
int master;
|
||||
|
||||
spin_lock(>h->gth_lock);
|
||||
|
@ -535,6 +525,10 @@ static void intel_th_gth_enable(struct intel_th_device *thdev,
|
|||
output->active = true;
|
||||
spin_unlock(>h->gth_lock);
|
||||
|
||||
scrpd = ioread32(gth->base + REG_GTH_SCRPD0);
|
||||
scrpd |= output->scratchpad;
|
||||
iowrite32(scrpd, gth->base + REG_GTH_SCRPD0);
|
||||
|
||||
iowrite32(scr, gth->base + REG_GTH_SCR);
|
||||
iowrite32(0, gth->base + REG_GTH_SCR2);
|
||||
}
|
||||
|
|
|
@ -57,9 +57,6 @@ enum {
|
|||
REG_GTH_SCRPD3 = 0xec, /* ScratchPad[3] */
|
||||
};
|
||||
|
||||
/* Externall debugger is using Intel TH */
|
||||
#define SCRPD_DEBUGGER_IN_USE BIT(24)
|
||||
|
||||
/* waiting for Pipeline Empty bit(s) to assert for GTH */
|
||||
#define GTH_PLE_WAITLOOP_DEPTH 10000
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ enum {
|
|||
* struct intel_th_output - descriptor INTEL_TH_OUTPUT type devices
|
||||
* @port: output port number, assigned by the switch
|
||||
* @type: GTH_{MSU,CTP,PTI}
|
||||
* @scratchpad: scratchpad bits to flag when this output is enabled
|
||||
* @multiblock: true for multiblock output configuration
|
||||
* @active: true when this output is enabled
|
||||
*
|
||||
|
@ -41,6 +42,7 @@ enum {
|
|||
struct intel_th_output {
|
||||
int port;
|
||||
unsigned int type;
|
||||
unsigned int scratchpad;
|
||||
bool multiblock;
|
||||
bool active;
|
||||
};
|
||||
|
@ -241,4 +243,43 @@ enum {
|
|||
GTH_PTI = 4, /* MIPI-PTI */
|
||||
};
|
||||
|
||||
/*
|
||||
* Scratchpad bits: tell firmware and external debuggers
|
||||
* what we are up to.
|
||||
*/
|
||||
enum {
|
||||
/* Memory is the primary destination */
|
||||
SCRPD_MEM_IS_PRIM_DEST = BIT(0),
|
||||
/* XHCI DbC is the primary destination */
|
||||
SCRPD_DBC_IS_PRIM_DEST = BIT(1),
|
||||
/* PTI is the primary destination */
|
||||
SCRPD_PTI_IS_PRIM_DEST = BIT(2),
|
||||
/* BSSB is the primary destination */
|
||||
SCRPD_BSSB_IS_PRIM_DEST = BIT(3),
|
||||
/* PTI is the alternate destination */
|
||||
SCRPD_PTI_IS_ALT_DEST = BIT(4),
|
||||
/* BSSB is the alternate destination */
|
||||
SCRPD_BSSB_IS_ALT_DEST = BIT(5),
|
||||
/* DeepSx exit occurred */
|
||||
SCRPD_DEEPSX_EXIT = BIT(6),
|
||||
/* S4 exit occurred */
|
||||
SCRPD_S4_EXIT = BIT(7),
|
||||
/* S5 exit occurred */
|
||||
SCRPD_S5_EXIT = BIT(8),
|
||||
/* MSU controller 0/1 is enabled */
|
||||
SCRPD_MSC0_IS_ENABLED = BIT(9),
|
||||
SCRPD_MSC1_IS_ENABLED = BIT(10),
|
||||
/* Sx exit occurred */
|
||||
SCRPD_SX_EXIT = BIT(11),
|
||||
/* Trigger Unit is enabled */
|
||||
SCRPD_TRIGGER_IS_ENABLED = BIT(12),
|
||||
SCRPD_ODLA_IS_ENABLED = BIT(13),
|
||||
SCRPD_SOCHAP_IS_ENABLED = BIT(14),
|
||||
SCRPD_STH_IS_ENABLED = BIT(15),
|
||||
SCRPD_DCIH_IS_ENABLED = BIT(16),
|
||||
SCRPD_VER_IS_ENABLED = BIT(17),
|
||||
/* External debugger is using Intel TH */
|
||||
SCRPD_DEBUGGER_IN_USE = BIT(24),
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -408,7 +408,7 @@ msc_buffer_iterate(struct msc_iter *iter, size_t size, void *data,
|
|||
* Second time (wrap_count==1), it's just like any other block,
|
||||
* containing data in the range of [MSC_BDESC..data_bytes].
|
||||
*/
|
||||
if (iter->block == iter->start_block && iter->wrap_count) {
|
||||
if (iter->block == iter->start_block && iter->wrap_count == 2) {
|
||||
tocopy = DATA_IN_PAGE - data_bytes;
|
||||
src += data_bytes;
|
||||
}
|
||||
|
@ -1112,12 +1112,11 @@ static ssize_t intel_th_msc_read(struct file *file, char __user *buf,
|
|||
size = msc->nr_pages << PAGE_SHIFT;
|
||||
|
||||
if (!size)
|
||||
return 0;
|
||||
|
||||
if (off >= size) {
|
||||
len = 0;
|
||||
goto put_count;
|
||||
}
|
||||
|
||||
if (off >= size)
|
||||
goto put_count;
|
||||
|
||||
if (off + len >= size)
|
||||
len = size - off;
|
||||
|
||||
|
|
|
@ -46,8 +46,6 @@ static int intel_th_pci_probe(struct pci_dev *pdev,
|
|||
if (IS_ERR(th))
|
||||
return PTR_ERR(th);
|
||||
|
||||
pci_set_drvdata(pdev, th);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -67,6 +65,16 @@ static const struct pci_device_id intel_th_pci_id_table[] = {
|
|||
PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xa126),
|
||||
.driver_data = (kernel_ulong_t)0,
|
||||
},
|
||||
{
|
||||
/* Apollo Lake */
|
||||
PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x5a8e),
|
||||
.driver_data = (kernel_ulong_t)0,
|
||||
},
|
||||
{
|
||||
/* Broxton */
|
||||
PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x0a80),
|
||||
.driver_data = (kernel_ulong_t)0,
|
||||
},
|
||||
{ 0 },
|
||||
};
|
||||
|
||||
|
|
|
@ -94,10 +94,13 @@ static ssize_t sth_stm_packet(struct stm_data *stm_data, unsigned int master,
|
|||
case STP_PACKET_TRIG:
|
||||
if (flags & STP_PACKET_TIMESTAMPED)
|
||||
reg += 4;
|
||||
iowrite8(*payload, sth->base + reg);
|
||||
writeb_relaxed(*payload, sth->base + reg);
|
||||
break;
|
||||
|
||||
case STP_PACKET_MERR:
|
||||
if (size > 4)
|
||||
size = 4;
|
||||
|
||||
sth_iowrite(&out->MERR, payload, size);
|
||||
break;
|
||||
|
||||
|
@ -107,8 +110,8 @@ static ssize_t sth_stm_packet(struct stm_data *stm_data, unsigned int master,
|
|||
else
|
||||
outp = (u64 __iomem *)&out->FLAG;
|
||||
|
||||
size = 1;
|
||||
sth_iowrite(outp, payload, size);
|
||||
size = 0;
|
||||
writeb_relaxed(0, outp);
|
||||
break;
|
||||
|
||||
case STP_PACKET_USER:
|
||||
|
@ -129,6 +132,8 @@ static ssize_t sth_stm_packet(struct stm_data *stm_data, unsigned int master,
|
|||
|
||||
sth_iowrite(outp, payload, size);
|
||||
break;
|
||||
default:
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
|
||||
return size;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
config STM
|
||||
tristate "System Trace Module devices"
|
||||
select CONFIGFS_FS
|
||||
select SRCU
|
||||
help
|
||||
A System Trace Module (STM) is a device exporting data in System
|
||||
Trace Protocol (STP) format as defined by MIPI STP standards.
|
||||
|
@ -8,6 +9,8 @@ config STM
|
|||
|
||||
Say Y here to enable System Trace Module device support.
|
||||
|
||||
if STM
|
||||
|
||||
config STM_DUMMY
|
||||
tristate "Dummy STM driver"
|
||||
help
|
||||
|
@ -24,3 +27,16 @@ config STM_SOURCE_CONSOLE
|
|||
|
||||
If you want to send kernel console messages over STM devices,
|
||||
say Y.
|
||||
|
||||
config STM_SOURCE_HEARTBEAT
|
||||
tristate "Heartbeat over STM devices"
|
||||
help
|
||||
This is a kernel space trace source that sends periodic
|
||||
heartbeat messages to trace hosts over STM devices. It is
|
||||
also useful for testing stm class drivers and the stm class
|
||||
framework itself.
|
||||
|
||||
If you want to send heartbeat messages over STM devices,
|
||||
say Y.
|
||||
|
||||
endif
|
||||
|
|
|
@ -5,5 +5,7 @@ stm_core-y := core.o policy.o
|
|||
obj-$(CONFIG_STM_DUMMY) += dummy_stm.o
|
||||
|
||||
obj-$(CONFIG_STM_SOURCE_CONSOLE) += stm_console.o
|
||||
obj-$(CONFIG_STM_SOURCE_HEARTBEAT) += stm_heartbeat.o
|
||||
|
||||
stm_console-y := console.o
|
||||
stm_heartbeat-y := heartbeat.o
|
||||
|
|
|
@ -113,6 +113,7 @@ struct stm_device *stm_find_device(const char *buf)
|
|||
|
||||
stm = to_stm_device(dev);
|
||||
if (!try_module_get(stm->owner)) {
|
||||
/* matches class_find_device() above */
|
||||
put_device(dev);
|
||||
return NULL;
|
||||
}
|
||||
|
@ -125,7 +126,7 @@ struct stm_device *stm_find_device(const char *buf)
|
|||
* @stm: stm device, previously acquired by stm_find_device()
|
||||
*
|
||||
* This drops the module reference and device reference taken by
|
||||
* stm_find_device().
|
||||
* stm_find_device() or stm_char_open().
|
||||
*/
|
||||
void stm_put_device(struct stm_device *stm)
|
||||
{
|
||||
|
@ -185,6 +186,9 @@ static void stm_output_claim(struct stm_device *stm, struct stm_output *output)
|
|||
{
|
||||
struct stp_master *master = stm_master(stm, output->master);
|
||||
|
||||
lockdep_assert_held(&stm->mc_lock);
|
||||
lockdep_assert_held(&output->lock);
|
||||
|
||||
if (WARN_ON_ONCE(master->nr_free < output->nr_chans))
|
||||
return;
|
||||
|
||||
|
@ -199,6 +203,9 @@ stm_output_disclaim(struct stm_device *stm, struct stm_output *output)
|
|||
{
|
||||
struct stp_master *master = stm_master(stm, output->master);
|
||||
|
||||
lockdep_assert_held(&stm->mc_lock);
|
||||
lockdep_assert_held(&output->lock);
|
||||
|
||||
bitmap_release_region(&master->chan_map[0], output->channel,
|
||||
ilog2(output->nr_chans));
|
||||
|
||||
|
@ -233,7 +240,7 @@ static int find_free_channels(unsigned long *bitmap, unsigned int start,
|
|||
return -1;
|
||||
}
|
||||
|
||||
static unsigned int
|
||||
static int
|
||||
stm_find_master_chan(struct stm_device *stm, unsigned int width,
|
||||
unsigned int *mstart, unsigned int mend,
|
||||
unsigned int *cstart, unsigned int cend)
|
||||
|
@ -288,12 +295,13 @@ static int stm_output_assign(struct stm_device *stm, unsigned int width,
|
|||
}
|
||||
|
||||
spin_lock(&stm->mc_lock);
|
||||
spin_lock(&output->lock);
|
||||
/* output is already assigned -- shouldn't happen */
|
||||
if (WARN_ON_ONCE(output->nr_chans))
|
||||
goto unlock;
|
||||
|
||||
ret = stm_find_master_chan(stm, width, &midx, mend, &cidx, cend);
|
||||
if (ret)
|
||||
if (ret < 0)
|
||||
goto unlock;
|
||||
|
||||
output->master = midx;
|
||||
|
@ -304,6 +312,7 @@ static int stm_output_assign(struct stm_device *stm, unsigned int width,
|
|||
|
||||
ret = 0;
|
||||
unlock:
|
||||
spin_unlock(&output->lock);
|
||||
spin_unlock(&stm->mc_lock);
|
||||
|
||||
return ret;
|
||||
|
@ -312,11 +321,18 @@ unlock:
|
|||
static void stm_output_free(struct stm_device *stm, struct stm_output *output)
|
||||
{
|
||||
spin_lock(&stm->mc_lock);
|
||||
spin_lock(&output->lock);
|
||||
if (output->nr_chans)
|
||||
stm_output_disclaim(stm, output);
|
||||
spin_unlock(&output->lock);
|
||||
spin_unlock(&stm->mc_lock);
|
||||
}
|
||||
|
||||
static void stm_output_init(struct stm_output *output)
|
||||
{
|
||||
spin_lock_init(&output->lock);
|
||||
}
|
||||
|
||||
static int major_match(struct device *dev, const void *data)
|
||||
{
|
||||
unsigned int major = *(unsigned int *)data;
|
||||
|
@ -339,6 +355,7 @@ static int stm_char_open(struct inode *inode, struct file *file)
|
|||
if (!stmf)
|
||||
return -ENOMEM;
|
||||
|
||||
stm_output_init(&stmf->output);
|
||||
stmf->stm = to_stm_device(dev);
|
||||
|
||||
if (!try_module_get(stmf->stm->owner))
|
||||
|
@ -349,6 +366,8 @@ static int stm_char_open(struct inode *inode, struct file *file)
|
|||
return nonseekable_open(inode, file);
|
||||
|
||||
err_free:
|
||||
/* matches class_find_device() above */
|
||||
put_device(dev);
|
||||
kfree(stmf);
|
||||
|
||||
return err;
|
||||
|
@ -357,9 +376,19 @@ err_free:
|
|||
static int stm_char_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct stm_file *stmf = file->private_data;
|
||||
struct stm_device *stm = stmf->stm;
|
||||
|
||||
stm_output_free(stmf->stm, &stmf->output);
|
||||
stm_put_device(stmf->stm);
|
||||
if (stm->data->unlink)
|
||||
stm->data->unlink(stm->data, stmf->output.master,
|
||||
stmf->output.channel);
|
||||
|
||||
stm_output_free(stm, &stmf->output);
|
||||
|
||||
/*
|
||||
* matches the stm_char_open()'s
|
||||
* class_find_device() + try_module_get()
|
||||
*/
|
||||
stm_put_device(stm);
|
||||
kfree(stmf);
|
||||
|
||||
return 0;
|
||||
|
@ -380,8 +409,8 @@ static int stm_file_assign(struct stm_file *stmf, char *id, unsigned int width)
|
|||
return ret;
|
||||
}
|
||||
|
||||
static void stm_write(struct stm_data *data, unsigned int master,
|
||||
unsigned int channel, const char *buf, size_t count)
|
||||
static ssize_t stm_write(struct stm_data *data, unsigned int master,
|
||||
unsigned int channel, const char *buf, size_t count)
|
||||
{
|
||||
unsigned int flags = STP_PACKET_TIMESTAMPED;
|
||||
const unsigned char *p = buf, nil = 0;
|
||||
|
@ -393,9 +422,14 @@ static void stm_write(struct stm_data *data, unsigned int master,
|
|||
sz = data->packet(data, master, channel, STP_PACKET_DATA, flags,
|
||||
sz, p);
|
||||
flags = 0;
|
||||
|
||||
if (sz < 0)
|
||||
break;
|
||||
}
|
||||
|
||||
data->packet(data, master, channel, STP_PACKET_FLAG, 0, 0, &nil);
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
static ssize_t stm_char_write(struct file *file, const char __user *buf,
|
||||
|
@ -406,6 +440,9 @@ static ssize_t stm_char_write(struct file *file, const char __user *buf,
|
|||
char *kbuf;
|
||||
int err;
|
||||
|
||||
if (count + 1 > PAGE_SIZE)
|
||||
count = PAGE_SIZE - 1;
|
||||
|
||||
/*
|
||||
* if no m/c have been assigned to this writer up to this
|
||||
* point, use "default" policy entry
|
||||
|
@ -430,8 +467,8 @@ static ssize_t stm_char_write(struct file *file, const char __user *buf,
|
|||
return -EFAULT;
|
||||
}
|
||||
|
||||
stm_write(stm->data, stmf->output.master, stmf->output.channel, kbuf,
|
||||
count);
|
||||
count = stm_write(stm->data, stmf->output.master, stmf->output.channel,
|
||||
kbuf, count);
|
||||
|
||||
kfree(kbuf);
|
||||
|
||||
|
@ -515,10 +552,8 @@ static int stm_char_policy_set_ioctl(struct stm_file *stmf, void __user *arg)
|
|||
ret = stm->data->link(stm->data, stmf->output.master,
|
||||
stmf->output.channel);
|
||||
|
||||
if (ret) {
|
||||
if (ret)
|
||||
stm_output_free(stmf->stm, &stmf->output);
|
||||
stm_put_device(stmf->stm);
|
||||
}
|
||||
|
||||
err_free:
|
||||
kfree(id);
|
||||
|
@ -618,7 +653,7 @@ int stm_register_device(struct device *parent, struct stm_data *stm_data,
|
|||
if (!stm_data->packet || !stm_data->sw_nchannels)
|
||||
return -EINVAL;
|
||||
|
||||
nmasters = stm_data->sw_end - stm_data->sw_start;
|
||||
nmasters = stm_data->sw_end - stm_data->sw_start + 1;
|
||||
stm = kzalloc(sizeof(*stm) + nmasters * sizeof(void *), GFP_KERNEL);
|
||||
if (!stm)
|
||||
return -ENOMEM;
|
||||
|
@ -641,6 +676,7 @@ int stm_register_device(struct device *parent, struct stm_data *stm_data,
|
|||
if (err)
|
||||
goto err_device;
|
||||
|
||||
mutex_init(&stm->link_mutex);
|
||||
spin_lock_init(&stm->link_lock);
|
||||
INIT_LIST_HEAD(&stm->link_list);
|
||||
|
||||
|
@ -654,6 +690,7 @@ int stm_register_device(struct device *parent, struct stm_data *stm_data,
|
|||
return 0;
|
||||
|
||||
err_device:
|
||||
/* matches device_initialize() above */
|
||||
put_device(&stm->dev);
|
||||
err_free:
|
||||
kfree(stm);
|
||||
|
@ -662,20 +699,28 @@ err_free:
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(stm_register_device);
|
||||
|
||||
static void __stm_source_link_drop(struct stm_source_device *src,
|
||||
struct stm_device *stm);
|
||||
static int __stm_source_link_drop(struct stm_source_device *src,
|
||||
struct stm_device *stm);
|
||||
|
||||
void stm_unregister_device(struct stm_data *stm_data)
|
||||
{
|
||||
struct stm_device *stm = stm_data->stm;
|
||||
struct stm_source_device *src, *iter;
|
||||
int i;
|
||||
int i, ret;
|
||||
|
||||
spin_lock(&stm->link_lock);
|
||||
mutex_lock(&stm->link_mutex);
|
||||
list_for_each_entry_safe(src, iter, &stm->link_list, link_entry) {
|
||||
__stm_source_link_drop(src, stm);
|
||||
ret = __stm_source_link_drop(src, stm);
|
||||
/*
|
||||
* src <-> stm link must not change under the same
|
||||
* stm::link_mutex, so complain loudly if it has;
|
||||
* also in this situation ret!=0 means this src is
|
||||
* not connected to this stm and it should be otherwise
|
||||
* safe to proceed with the tear-down of stm.
|
||||
*/
|
||||
WARN_ON_ONCE(ret);
|
||||
}
|
||||
spin_unlock(&stm->link_lock);
|
||||
mutex_unlock(&stm->link_mutex);
|
||||
|
||||
synchronize_srcu(&stm_source_srcu);
|
||||
|
||||
|
@ -686,7 +731,7 @@ void stm_unregister_device(struct stm_data *stm_data)
|
|||
stp_policy_unbind(stm->policy);
|
||||
mutex_unlock(&stm->policy_mutex);
|
||||
|
||||
for (i = 0; i < stm->sw_nmasters; i++)
|
||||
for (i = stm->data->sw_start; i <= stm->data->sw_end; i++)
|
||||
stp_master_free(stm, i);
|
||||
|
||||
device_unregister(&stm->dev);
|
||||
|
@ -694,6 +739,17 @@ void stm_unregister_device(struct stm_data *stm_data)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(stm_unregister_device);
|
||||
|
||||
/*
|
||||
* stm::link_list access serialization uses a spinlock and a mutex; holding
|
||||
* either of them guarantees that the list is stable; modification requires
|
||||
* holding both of them.
|
||||
*
|
||||
* Lock ordering is as follows:
|
||||
* stm::link_mutex
|
||||
* stm::link_lock
|
||||
* src::link_lock
|
||||
*/
|
||||
|
||||
/**
|
||||
* stm_source_link_add() - connect an stm_source device to an stm device
|
||||
* @src: stm_source device
|
||||
|
@ -710,6 +766,7 @@ static int stm_source_link_add(struct stm_source_device *src,
|
|||
char *id;
|
||||
int err;
|
||||
|
||||
mutex_lock(&stm->link_mutex);
|
||||
spin_lock(&stm->link_lock);
|
||||
spin_lock(&src->link_lock);
|
||||
|
||||
|
@ -719,6 +776,7 @@ static int stm_source_link_add(struct stm_source_device *src,
|
|||
|
||||
spin_unlock(&src->link_lock);
|
||||
spin_unlock(&stm->link_lock);
|
||||
mutex_unlock(&stm->link_mutex);
|
||||
|
||||
id = kstrdup(src->data->name, GFP_KERNEL);
|
||||
if (id) {
|
||||
|
@ -753,9 +811,9 @@ static int stm_source_link_add(struct stm_source_device *src,
|
|||
|
||||
fail_free_output:
|
||||
stm_output_free(stm, &src->output);
|
||||
stm_put_device(stm);
|
||||
|
||||
fail_detach:
|
||||
mutex_lock(&stm->link_mutex);
|
||||
spin_lock(&stm->link_lock);
|
||||
spin_lock(&src->link_lock);
|
||||
|
||||
|
@ -764,6 +822,7 @@ fail_detach:
|
|||
|
||||
spin_unlock(&src->link_lock);
|
||||
spin_unlock(&stm->link_lock);
|
||||
mutex_unlock(&stm->link_mutex);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
@ -776,28 +835,55 @@ fail_detach:
|
|||
* If @stm is @src::link, disconnect them from one another and put the
|
||||
* reference on the @stm device.
|
||||
*
|
||||
* Caller must hold stm::link_lock.
|
||||
* Caller must hold stm::link_mutex.
|
||||
*/
|
||||
static void __stm_source_link_drop(struct stm_source_device *src,
|
||||
struct stm_device *stm)
|
||||
static int __stm_source_link_drop(struct stm_source_device *src,
|
||||
struct stm_device *stm)
|
||||
{
|
||||
struct stm_device *link;
|
||||
int ret = 0;
|
||||
|
||||
lockdep_assert_held(&stm->link_mutex);
|
||||
|
||||
/* for stm::link_list modification, we hold both mutex and spinlock */
|
||||
spin_lock(&stm->link_lock);
|
||||
spin_lock(&src->link_lock);
|
||||
link = srcu_dereference_check(src->link, &stm_source_srcu, 1);
|
||||
if (WARN_ON_ONCE(link != stm)) {
|
||||
spin_unlock(&src->link_lock);
|
||||
return;
|
||||
|
||||
/*
|
||||
* The linked device may have changed since we last looked, because
|
||||
* we weren't holding the src::link_lock back then; if this is the
|
||||
* case, tell the caller to retry.
|
||||
*/
|
||||
if (link != stm) {
|
||||
ret = -EAGAIN;
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
stm_output_free(link, &src->output);
|
||||
/* caller must hold stm::link_lock */
|
||||
list_del_init(&src->link_entry);
|
||||
/* matches stm_find_device() from stm_source_link_store() */
|
||||
stm_put_device(link);
|
||||
rcu_assign_pointer(src->link, NULL);
|
||||
|
||||
unlock:
|
||||
spin_unlock(&src->link_lock);
|
||||
spin_unlock(&stm->link_lock);
|
||||
|
||||
/*
|
||||
* Call the unlink callbacks for both source and stm, when we know
|
||||
* that we have actually performed the unlinking.
|
||||
*/
|
||||
if (!ret) {
|
||||
if (src->data->unlink)
|
||||
src->data->unlink(src->data);
|
||||
|
||||
if (stm->data->unlink)
|
||||
stm->data->unlink(stm->data, src->output.master,
|
||||
src->output.channel);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -813,21 +899,29 @@ static void __stm_source_link_drop(struct stm_source_device *src,
|
|||
static void stm_source_link_drop(struct stm_source_device *src)
|
||||
{
|
||||
struct stm_device *stm;
|
||||
int idx;
|
||||
int idx, ret;
|
||||
|
||||
retry:
|
||||
idx = srcu_read_lock(&stm_source_srcu);
|
||||
/*
|
||||
* The stm device will be valid for the duration of this
|
||||
* read section, but the link may change before we grab
|
||||
* the src::link_lock in __stm_source_link_drop().
|
||||
*/
|
||||
stm = srcu_dereference(src->link, &stm_source_srcu);
|
||||
|
||||
ret = 0;
|
||||
if (stm) {
|
||||
if (src->data->unlink)
|
||||
src->data->unlink(src->data);
|
||||
|
||||
spin_lock(&stm->link_lock);
|
||||
__stm_source_link_drop(src, stm);
|
||||
spin_unlock(&stm->link_lock);
|
||||
mutex_lock(&stm->link_mutex);
|
||||
ret = __stm_source_link_drop(src, stm);
|
||||
mutex_unlock(&stm->link_mutex);
|
||||
}
|
||||
|
||||
srcu_read_unlock(&stm_source_srcu, idx);
|
||||
|
||||
/* if it did change, retry */
|
||||
if (ret == -EAGAIN)
|
||||
goto retry;
|
||||
}
|
||||
|
||||
static ssize_t stm_source_link_show(struct device *dev,
|
||||
|
@ -862,8 +956,10 @@ static ssize_t stm_source_link_store(struct device *dev,
|
|||
return -EINVAL;
|
||||
|
||||
err = stm_source_link_add(src, link);
|
||||
if (err)
|
||||
if (err) {
|
||||
/* matches the stm_find_device() above */
|
||||
stm_put_device(link);
|
||||
}
|
||||
|
||||
return err ? : count;
|
||||
}
|
||||
|
@ -925,6 +1021,7 @@ int stm_source_register_device(struct device *parent,
|
|||
if (err)
|
||||
goto err;
|
||||
|
||||
stm_output_init(&src->output);
|
||||
spin_lock_init(&src->link_lock);
|
||||
INIT_LIST_HEAD(&src->link_entry);
|
||||
src->data = data;
|
||||
|
@ -973,9 +1070,9 @@ int stm_source_write(struct stm_source_data *data, unsigned int chan,
|
|||
|
||||
stm = srcu_dereference(src->link, &stm_source_srcu);
|
||||
if (stm)
|
||||
stm_write(stm->data, src->output.master,
|
||||
src->output.channel + chan,
|
||||
buf, count);
|
||||
count = stm_write(stm->data, src->output.master,
|
||||
src->output.channel + chan,
|
||||
buf, count);
|
||||
else
|
||||
count = -ENODEV;
|
||||
|
||||
|
|
|
@ -40,22 +40,75 @@ dummy_stm_packet(struct stm_data *stm_data, unsigned int master,
|
|||
return size;
|
||||
}
|
||||
|
||||
static struct stm_data dummy_stm = {
|
||||
.name = "dummy_stm",
|
||||
.sw_start = 0x0000,
|
||||
.sw_end = 0xffff,
|
||||
.sw_nchannels = 0xffff,
|
||||
.packet = dummy_stm_packet,
|
||||
};
|
||||
#define DUMMY_STM_MAX 32
|
||||
|
||||
static struct stm_data dummy_stm[DUMMY_STM_MAX];
|
||||
|
||||
static int nr_dummies = 4;
|
||||
|
||||
module_param(nr_dummies, int, 0600);
|
||||
|
||||
static unsigned int dummy_stm_nr;
|
||||
|
||||
static unsigned int fail_mode;
|
||||
|
||||
module_param(fail_mode, int, 0600);
|
||||
|
||||
static int dummy_stm_link(struct stm_data *data, unsigned int master,
|
||||
unsigned int channel)
|
||||
{
|
||||
if (fail_mode && (channel & fail_mode))
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dummy_stm_init(void)
|
||||
{
|
||||
return stm_register_device(NULL, &dummy_stm, THIS_MODULE);
|
||||
int i, ret = -ENOMEM, __nr_dummies = ACCESS_ONCE(nr_dummies);
|
||||
|
||||
if (__nr_dummies < 0 || __nr_dummies > DUMMY_STM_MAX)
|
||||
return -EINVAL;
|
||||
|
||||
for (i = 0; i < __nr_dummies; i++) {
|
||||
dummy_stm[i].name = kasprintf(GFP_KERNEL, "dummy_stm.%d", i);
|
||||
if (!dummy_stm[i].name)
|
||||
goto fail_unregister;
|
||||
|
||||
dummy_stm[i].sw_start = 0x0000;
|
||||
dummy_stm[i].sw_end = 0xffff;
|
||||
dummy_stm[i].sw_nchannels = 0xffff;
|
||||
dummy_stm[i].packet = dummy_stm_packet;
|
||||
dummy_stm[i].link = dummy_stm_link;
|
||||
|
||||
ret = stm_register_device(NULL, &dummy_stm[i], THIS_MODULE);
|
||||
if (ret)
|
||||
goto fail_free;
|
||||
}
|
||||
|
||||
dummy_stm_nr = __nr_dummies;
|
||||
|
||||
return 0;
|
||||
|
||||
fail_unregister:
|
||||
for (i--; i >= 0; i--) {
|
||||
stm_unregister_device(&dummy_stm[i]);
|
||||
fail_free:
|
||||
kfree(dummy_stm[i].name);
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
static void dummy_stm_exit(void)
|
||||
{
|
||||
stm_unregister_device(&dummy_stm);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < dummy_stm_nr; i++) {
|
||||
stm_unregister_device(&dummy_stm[i]);
|
||||
kfree(dummy_stm[i].name);
|
||||
}
|
||||
}
|
||||
|
||||
module_init(dummy_stm_init);
|
||||
|
|
|
@ -0,0 +1,130 @@
|
|||
/*
|
||||
* Simple heartbeat STM source driver
|
||||
* Copyright (c) 2016, Intel Corporation.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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.
|
||||
*
|
||||
* Heartbeat STM source will send repetitive messages over STM devices to a
|
||||
* trace host.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/hrtimer.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/stm.h>
|
||||
|
||||
#define STM_HEARTBEAT_MAX 32
|
||||
|
||||
static int nr_devs = 4;
|
||||
static int interval_ms = 10;
|
||||
|
||||
module_param(nr_devs, int, 0600);
|
||||
module_param(interval_ms, int, 0600);
|
||||
|
||||
static struct stm_heartbeat {
|
||||
struct stm_source_data data;
|
||||
struct hrtimer hrtimer;
|
||||
unsigned int active;
|
||||
} stm_heartbeat[STM_HEARTBEAT_MAX];
|
||||
|
||||
static unsigned int nr_instances;
|
||||
|
||||
static const char str[] = "heartbeat stm source driver is here to serve you";
|
||||
|
||||
static enum hrtimer_restart stm_heartbeat_hrtimer_handler(struct hrtimer *hr)
|
||||
{
|
||||
struct stm_heartbeat *heartbeat = container_of(hr, struct stm_heartbeat,
|
||||
hrtimer);
|
||||
|
||||
stm_source_write(&heartbeat->data, 0, str, sizeof str);
|
||||
if (heartbeat->active)
|
||||
hrtimer_forward_now(hr, ms_to_ktime(interval_ms));
|
||||
|
||||
return heartbeat->active ? HRTIMER_RESTART : HRTIMER_NORESTART;
|
||||
}
|
||||
|
||||
static int stm_heartbeat_link(struct stm_source_data *data)
|
||||
{
|
||||
struct stm_heartbeat *heartbeat =
|
||||
container_of(data, struct stm_heartbeat, data);
|
||||
|
||||
heartbeat->active = 1;
|
||||
hrtimer_start(&heartbeat->hrtimer, ms_to_ktime(interval_ms),
|
||||
HRTIMER_MODE_ABS);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void stm_heartbeat_unlink(struct stm_source_data *data)
|
||||
{
|
||||
struct stm_heartbeat *heartbeat =
|
||||
container_of(data, struct stm_heartbeat, data);
|
||||
|
||||
heartbeat->active = 0;
|
||||
hrtimer_cancel(&heartbeat->hrtimer);
|
||||
}
|
||||
|
||||
static int stm_heartbeat_init(void)
|
||||
{
|
||||
int i, ret = -ENOMEM, __nr_instances = ACCESS_ONCE(nr_devs);
|
||||
|
||||
if (__nr_instances < 0 || __nr_instances > STM_HEARTBEAT_MAX)
|
||||
return -EINVAL;
|
||||
|
||||
for (i = 0; i < __nr_instances; i++) {
|
||||
stm_heartbeat[i].data.name =
|
||||
kasprintf(GFP_KERNEL, "heartbeat.%d", i);
|
||||
if (!stm_heartbeat[i].data.name)
|
||||
goto fail_unregister;
|
||||
|
||||
stm_heartbeat[i].data.nr_chans = 1;
|
||||
stm_heartbeat[i].data.link = stm_heartbeat_link;
|
||||
stm_heartbeat[i].data.unlink = stm_heartbeat_unlink;
|
||||
hrtimer_init(&stm_heartbeat[i].hrtimer, CLOCK_MONOTONIC,
|
||||
HRTIMER_MODE_ABS);
|
||||
stm_heartbeat[i].hrtimer.function =
|
||||
stm_heartbeat_hrtimer_handler;
|
||||
|
||||
ret = stm_source_register_device(NULL, &stm_heartbeat[i].data);
|
||||
if (ret)
|
||||
goto fail_free;
|
||||
}
|
||||
|
||||
nr_instances = __nr_instances;
|
||||
|
||||
return 0;
|
||||
|
||||
fail_unregister:
|
||||
for (i--; i >= 0; i--) {
|
||||
stm_source_unregister_device(&stm_heartbeat[i].data);
|
||||
fail_free:
|
||||
kfree(stm_heartbeat[i].data.name);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void stm_heartbeat_exit(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < nr_instances; i++) {
|
||||
stm_source_unregister_device(&stm_heartbeat[i].data);
|
||||
kfree(stm_heartbeat[i].data.name);
|
||||
}
|
||||
}
|
||||
|
||||
module_init(stm_heartbeat_init);
|
||||
module_exit(stm_heartbeat_exit);
|
||||
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_DESCRIPTION("stm_heartbeat driver");
|
||||
MODULE_AUTHOR("Alexander Shishkin <alexander.shishkin@linux.intel.com>");
|
|
@ -272,13 +272,17 @@ void stp_policy_unbind(struct stp_policy *policy)
|
|||
{
|
||||
struct stm_device *stm = policy->stm;
|
||||
|
||||
/*
|
||||
* stp_policy_release() will not call here if the policy is already
|
||||
* unbound; other users should not either, as no link exists between
|
||||
* this policy and anything else in that case
|
||||
*/
|
||||
if (WARN_ON_ONCE(!policy->stm))
|
||||
return;
|
||||
|
||||
mutex_lock(&stm->policy_mutex);
|
||||
stm->policy = NULL;
|
||||
mutex_unlock(&stm->policy_mutex);
|
||||
lockdep_assert_held(&stm->policy_mutex);
|
||||
|
||||
stm->policy = NULL;
|
||||
policy->stm = NULL;
|
||||
|
||||
stm_put_device(stm);
|
||||
|
@ -287,8 +291,16 @@ void stp_policy_unbind(struct stp_policy *policy)
|
|||
static void stp_policy_release(struct config_item *item)
|
||||
{
|
||||
struct stp_policy *policy = to_stp_policy(item);
|
||||
struct stm_device *stm = policy->stm;
|
||||
|
||||
/* a policy *can* be unbound and still exist in configfs tree */
|
||||
if (!stm)
|
||||
return;
|
||||
|
||||
mutex_lock(&stm->policy_mutex);
|
||||
stp_policy_unbind(policy);
|
||||
mutex_unlock(&stm->policy_mutex);
|
||||
|
||||
kfree(policy);
|
||||
}
|
||||
|
||||
|
@ -320,10 +332,11 @@ stp_policies_make(struct config_group *group, const char *name)
|
|||
|
||||
/*
|
||||
* node must look like <device_name>.<policy_name>, where
|
||||
* <device_name> is the name of an existing stm device and
|
||||
* <policy_name> is an arbitrary string
|
||||
* <device_name> is the name of an existing stm device; may
|
||||
* contain dots;
|
||||
* <policy_name> is an arbitrary string; may not contain dots
|
||||
*/
|
||||
p = strchr(devname, '.');
|
||||
p = strrchr(devname, '.');
|
||||
if (!p) {
|
||||
kfree(devname);
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
|
|
@ -45,6 +45,7 @@ struct stm_device {
|
|||
int major;
|
||||
unsigned int sw_nmasters;
|
||||
struct stm_data *data;
|
||||
struct mutex link_mutex;
|
||||
spinlock_t link_lock;
|
||||
struct list_head link_list;
|
||||
/* master allocation */
|
||||
|
@ -56,6 +57,7 @@ struct stm_device {
|
|||
container_of((_d), struct stm_device, dev)
|
||||
|
||||
struct stm_output {
|
||||
spinlock_t lock;
|
||||
unsigned int master;
|
||||
unsigned int channel;
|
||||
unsigned int nr_chans;
|
||||
|
|
|
@ -440,7 +440,7 @@ config ARM_CHARLCD
|
|||
still useful.
|
||||
|
||||
config BMP085
|
||||
bool
|
||||
tristate
|
||||
depends on SYSFS
|
||||
|
||||
config BMP085_I2C
|
||||
|
@ -470,7 +470,7 @@ config BMP085_SPI
|
|||
config PCH_PHUB
|
||||
tristate "Intel EG20T PCH/LAPIS Semicon IOH(ML7213/ML7223/ML7831) PHUB"
|
||||
select GENERIC_NET_UTILS
|
||||
depends on PCI && (X86_32 || COMPILE_TEST)
|
||||
depends on PCI && (X86_32 || MIPS || COMPILE_TEST)
|
||||
help
|
||||
This driver is for PCH(Platform controller Hub) PHUB(Packet Hub) of
|
||||
Intel Topcliff which is an IOH(Input/Output Hub) for x86 embedded
|
||||
|
|
|
@ -216,7 +216,7 @@ static s32 dpot_read_i2c(struct dpot_data *dpot, u8 reg)
|
|||
*/
|
||||
value = swab16(value);
|
||||
|
||||
if (dpot->uid == DPOT_UID(AD5271_ID))
|
||||
if (dpot->uid == DPOT_UID(AD5274_ID))
|
||||
value = value >> 2;
|
||||
return value;
|
||||
default:
|
||||
|
@ -452,7 +452,7 @@ static ssize_t sysfs_set_reg(struct device *dev,
|
|||
int err;
|
||||
|
||||
if (reg & DPOT_ADDR_OTP_EN) {
|
||||
if (!strncmp(buf, "enabled", sizeof("enabled")))
|
||||
if (sysfs_streq(buf, "enabled"))
|
||||
set_bit(DPOT_RDAC_MASK & reg, data->otp_en_mask);
|
||||
else
|
||||
clear_bit(DPOT_RDAC_MASK & reg, data->otp_en_mask);
|
||||
|
|
|
@ -1215,7 +1215,7 @@ static int apds990x_remove(struct i2c_client *client)
|
|||
#ifdef CONFIG_PM_SLEEP
|
||||
static int apds990x_suspend(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = container_of(dev, struct i2c_client, dev);
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct apds990x_chip *chip = i2c_get_clientdata(client);
|
||||
|
||||
apds990x_chip_off(chip);
|
||||
|
@ -1224,7 +1224,7 @@ static int apds990x_suspend(struct device *dev)
|
|||
|
||||
static int apds990x_resume(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = container_of(dev, struct i2c_client, dev);
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct apds990x_chip *chip = i2c_get_clientdata(client);
|
||||
|
||||
/*
|
||||
|
@ -1240,7 +1240,7 @@ static int apds990x_resume(struct device *dev)
|
|||
#ifdef CONFIG_PM
|
||||
static int apds990x_runtime_suspend(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = container_of(dev, struct i2c_client, dev);
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct apds990x_chip *chip = i2c_get_clientdata(client);
|
||||
|
||||
apds990x_chip_off(chip);
|
||||
|
@ -1249,7 +1249,7 @@ static int apds990x_runtime_suspend(struct device *dev)
|
|||
|
||||
static int apds990x_runtime_resume(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = container_of(dev, struct i2c_client, dev);
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct apds990x_chip *chip = i2c_get_clientdata(client);
|
||||
|
||||
apds990x_chip_on(chip);
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
* Author: Linus Walleij <triad@df.lth.se>
|
||||
*/
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/of.h>
|
||||
|
@ -328,20 +327,6 @@ out_no_resource:
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int __exit charlcd_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct charlcd *lcd = platform_get_drvdata(pdev);
|
||||
|
||||
if (lcd) {
|
||||
free_irq(lcd->irq, lcd);
|
||||
iounmap(lcd->virtbase);
|
||||
release_mem_region(lcd->phybase, lcd->physize);
|
||||
kfree(lcd);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int charlcd_suspend(struct device *dev)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
|
@ -376,13 +361,8 @@ static struct platform_driver charlcd_driver = {
|
|||
.driver = {
|
||||
.name = DRIVERNAME,
|
||||
.pm = &charlcd_pm_ops,
|
||||
.suppress_bind_attrs = true,
|
||||
.of_match_table = of_match_ptr(charlcd_match),
|
||||
},
|
||||
.remove = __exit_p(charlcd_remove),
|
||||
};
|
||||
|
||||
module_platform_driver_probe(charlcd_driver, charlcd_probe);
|
||||
|
||||
MODULE_AUTHOR("Linus Walleij <triad@df.lth.se>");
|
||||
MODULE_DESCRIPTION("ARM Character LCD Driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
builtin_platform_driver_probe(charlcd_driver, charlcd_probe);
|
||||
|
|
|
@ -1323,7 +1323,7 @@ static int bh1770_remove(struct i2c_client *client)
|
|||
#ifdef CONFIG_PM_SLEEP
|
||||
static int bh1770_suspend(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = container_of(dev, struct i2c_client, dev);
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct bh1770_chip *chip = i2c_get_clientdata(client);
|
||||
|
||||
bh1770_chip_off(chip);
|
||||
|
@ -1333,7 +1333,7 @@ static int bh1770_suspend(struct device *dev)
|
|||
|
||||
static int bh1770_resume(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = container_of(dev, struct i2c_client, dev);
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct bh1770_chip *chip = i2c_get_clientdata(client);
|
||||
int ret = 0;
|
||||
|
||||
|
@ -1361,7 +1361,7 @@ static int bh1770_resume(struct device *dev)
|
|||
#ifdef CONFIG_PM
|
||||
static int bh1770_runtime_suspend(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = container_of(dev, struct i2c_client, dev);
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct bh1770_chip *chip = i2c_get_clientdata(client);
|
||||
|
||||
bh1770_chip_off(chip);
|
||||
|
@ -1371,7 +1371,7 @@ static int bh1770_runtime_suspend(struct device *dev)
|
|||
|
||||
static int bh1770_runtime_resume(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = container_of(dev, struct i2c_client, dev);
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct bh1770_chip *chip = i2c_get_clientdata(client);
|
||||
|
||||
bh1770_chip_on(chip);
|
||||
|
|
|
@ -721,9 +721,7 @@ static ssize_t c2port_read_flash_data(struct file *filp, struct kobject *kobj,
|
|||
struct bin_attribute *attr,
|
||||
char *buffer, loff_t offset, size_t count)
|
||||
{
|
||||
struct c2port_device *c2dev =
|
||||
dev_get_drvdata(container_of(kobj,
|
||||
struct device, kobj));
|
||||
struct c2port_device *c2dev = dev_get_drvdata(kobj_to_dev(kobj));
|
||||
ssize_t ret;
|
||||
|
||||
/* Check the device and flash access status */
|
||||
|
@ -838,9 +836,7 @@ static ssize_t c2port_write_flash_data(struct file *filp, struct kobject *kobj,
|
|||
struct bin_attribute *attr,
|
||||
char *buffer, loff_t offset, size_t count)
|
||||
{
|
||||
struct c2port_device *c2dev =
|
||||
dev_get_drvdata(container_of(kobj,
|
||||
struct device, kobj));
|
||||
struct c2port_device *c2dev = dev_get_drvdata(kobj_to_dev(kobj));
|
||||
int ret;
|
||||
|
||||
/* Check the device access status */
|
||||
|
|
|
@ -386,8 +386,7 @@ static ssize_t afu_eb_read(struct file *filp, struct kobject *kobj,
|
|||
struct bin_attribute *bin_attr, char *buf,
|
||||
loff_t off, size_t count)
|
||||
{
|
||||
struct cxl_afu *afu = to_cxl_afu(container_of(kobj,
|
||||
struct device, kobj));
|
||||
struct cxl_afu *afu = to_cxl_afu(kobj_to_dev(kobj));
|
||||
|
||||
return cxl_afu_read_err_buffer(afu, buf, off, count);
|
||||
}
|
||||
|
@ -467,7 +466,7 @@ static ssize_t afu_read_config(struct file *filp, struct kobject *kobj,
|
|||
loff_t off, size_t count)
|
||||
{
|
||||
struct afu_config_record *cr = to_cr(kobj);
|
||||
struct cxl_afu *afu = to_cxl_afu(container_of(kobj->parent, struct device, kobj));
|
||||
struct cxl_afu *afu = to_cxl_afu(kobj_to_dev(kobj->parent));
|
||||
|
||||
u64 i, j, val;
|
||||
|
||||
|
|
|
@ -3,6 +3,8 @@ menu "EEPROM support"
|
|||
config EEPROM_AT24
|
||||
tristate "I2C EEPROMs / RAMs / ROMs from most vendors"
|
||||
depends on I2C && SYSFS
|
||||
select REGMAP
|
||||
select NVMEM
|
||||
help
|
||||
Enable this driver to get read/write support to most I2C EEPROMs
|
||||
and compatible devices like FRAMs, SRAMs, ROMs etc. After you
|
||||
|
@ -30,6 +32,8 @@ config EEPROM_AT24
|
|||
config EEPROM_AT25
|
||||
tristate "SPI EEPROMs from most vendors"
|
||||
depends on SPI && SYSFS
|
||||
select REGMAP
|
||||
select NVMEM
|
||||
help
|
||||
Enable this driver to get read/write support to most SPI EEPROMs,
|
||||
after you configure the board init code to know about each eeprom
|
||||
|
@ -74,6 +78,8 @@ config EEPROM_93CX6
|
|||
config EEPROM_93XX46
|
||||
tristate "Microwire EEPROM 93XX46 support"
|
||||
depends on SPI && SYSFS
|
||||
select REGMAP
|
||||
select NVMEM
|
||||
help
|
||||
Driver for the microwire EEPROM chipsets 93xx46x. The driver
|
||||
supports both read and write commands and also the command to
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
#include <linux/slab.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/log2.h>
|
||||
#include <linux/bitops.h>
|
||||
|
@ -23,6 +22,8 @@
|
|||
#include <linux/of.h>
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/nvmem-provider.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/platform_data/at24.h>
|
||||
|
||||
/*
|
||||
|
@ -55,7 +56,6 @@
|
|||
|
||||
struct at24_data {
|
||||
struct at24_platform_data chip;
|
||||
struct memory_accessor macc;
|
||||
int use_smbus;
|
||||
int use_smbus_write;
|
||||
|
||||
|
@ -64,12 +64,15 @@ struct at24_data {
|
|||
* but not from changes by other I2C masters.
|
||||
*/
|
||||
struct mutex lock;
|
||||
struct bin_attribute bin;
|
||||
|
||||
u8 *writebuf;
|
||||
unsigned write_max;
|
||||
unsigned num_addresses;
|
||||
|
||||
struct regmap_config regmap_config;
|
||||
struct nvmem_config nvmem_config;
|
||||
struct nvmem_device *nvmem;
|
||||
|
||||
/*
|
||||
* Some chips tie up multiple I2C addresses; dummy devices reserve
|
||||
* them for us, and we'll use them with SMBus calls.
|
||||
|
@ -283,17 +286,6 @@ static ssize_t at24_read(struct at24_data *at24,
|
|||
return retval;
|
||||
}
|
||||
|
||||
static ssize_t at24_bin_read(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *attr,
|
||||
char *buf, loff_t off, size_t count)
|
||||
{
|
||||
struct at24_data *at24;
|
||||
|
||||
at24 = dev_get_drvdata(container_of(kobj, struct device, kobj));
|
||||
return at24_read(at24, buf, off, count);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Note that if the hardware write-protect pin is pulled high, the whole
|
||||
* chip is normally write protected. But there are plenty of product
|
||||
|
@ -414,40 +406,49 @@ static ssize_t at24_write(struct at24_data *at24, const char *buf, loff_t off,
|
|||
return retval;
|
||||
}
|
||||
|
||||
static ssize_t at24_bin_write(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *attr,
|
||||
char *buf, loff_t off, size_t count)
|
||||
{
|
||||
struct at24_data *at24;
|
||||
|
||||
at24 = dev_get_drvdata(container_of(kobj, struct device, kobj));
|
||||
return at24_write(at24, buf, off, count);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* This lets other kernel code access the eeprom data. For example, it
|
||||
* might hold a board's Ethernet address, or board-specific calibration
|
||||
* data generated on the manufacturing floor.
|
||||
*/
|
||||
|
||||
static ssize_t at24_macc_read(struct memory_accessor *macc, char *buf,
|
||||
off_t offset, size_t count)
|
||||
* Provide a regmap interface, which is registered with the NVMEM
|
||||
* framework
|
||||
*/
|
||||
static int at24_regmap_read(void *context, const void *reg, size_t reg_size,
|
||||
void *val, size_t val_size)
|
||||
{
|
||||
struct at24_data *at24 = container_of(macc, struct at24_data, macc);
|
||||
struct at24_data *at24 = context;
|
||||
off_t offset = *(u32 *)reg;
|
||||
int err;
|
||||
|
||||
return at24_read(at24, buf, offset, count);
|
||||
err = at24_read(at24, val, offset, val_size);
|
||||
if (err)
|
||||
return err;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t at24_macc_write(struct memory_accessor *macc, const char *buf,
|
||||
off_t offset, size_t count)
|
||||
static int at24_regmap_write(void *context, const void *data, size_t count)
|
||||
{
|
||||
struct at24_data *at24 = container_of(macc, struct at24_data, macc);
|
||||
struct at24_data *at24 = context;
|
||||
const char *buf;
|
||||
u32 offset;
|
||||
size_t len;
|
||||
int err;
|
||||
|
||||
return at24_write(at24, buf, offset, count);
|
||||
memcpy(&offset, data, sizeof(offset));
|
||||
buf = (const char *)data + sizeof(offset);
|
||||
len = count - sizeof(offset);
|
||||
|
||||
err = at24_write(at24, buf, offset, len);
|
||||
if (err)
|
||||
return err;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct regmap_bus at24_regmap_bus = {
|
||||
.read = at24_regmap_read,
|
||||
.write = at24_regmap_write,
|
||||
.reg_format_endian_default = REGMAP_ENDIAN_NATIVE,
|
||||
};
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
|
@ -481,6 +482,7 @@ static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id)
|
|||
struct at24_data *at24;
|
||||
int err;
|
||||
unsigned i, num_addresses;
|
||||
struct regmap *regmap;
|
||||
|
||||
if (client->dev.platform_data) {
|
||||
chip = *(struct at24_platform_data *)client->dev.platform_data;
|
||||
|
@ -573,29 +575,12 @@ static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id)
|
|||
at24->chip = chip;
|
||||
at24->num_addresses = num_addresses;
|
||||
|
||||
/*
|
||||
* Export the EEPROM bytes through sysfs, since that's convenient.
|
||||
* By default, only root should see the data (maybe passwords etc)
|
||||
*/
|
||||
sysfs_bin_attr_init(&at24->bin);
|
||||
at24->bin.attr.name = "eeprom";
|
||||
at24->bin.attr.mode = chip.flags & AT24_FLAG_IRUGO ? S_IRUGO : S_IRUSR;
|
||||
at24->bin.read = at24_bin_read;
|
||||
at24->bin.size = chip.byte_len;
|
||||
|
||||
at24->macc.read = at24_macc_read;
|
||||
|
||||
writable = !(chip.flags & AT24_FLAG_READONLY);
|
||||
if (writable) {
|
||||
if (!use_smbus || use_smbus_write) {
|
||||
|
||||
unsigned write_max = chip.page_size;
|
||||
|
||||
at24->macc.write = at24_macc_write;
|
||||
|
||||
at24->bin.write = at24_bin_write;
|
||||
at24->bin.attr.mode |= S_IWUSR;
|
||||
|
||||
if (write_max > io_limit)
|
||||
write_max = io_limit;
|
||||
if (use_smbus && write_max > I2C_SMBUS_BLOCK_MAX)
|
||||
|
@ -627,14 +612,38 @@ static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id)
|
|||
}
|
||||
}
|
||||
|
||||
err = sysfs_create_bin_file(&client->dev.kobj, &at24->bin);
|
||||
if (err)
|
||||
at24->regmap_config.reg_bits = 32;
|
||||
at24->regmap_config.val_bits = 8;
|
||||
at24->regmap_config.reg_stride = 1;
|
||||
at24->regmap_config.max_register = chip.byte_len - 1;
|
||||
|
||||
regmap = devm_regmap_init(&client->dev, &at24_regmap_bus, at24,
|
||||
&at24->regmap_config);
|
||||
if (IS_ERR(regmap)) {
|
||||
dev_err(&client->dev, "regmap init failed\n");
|
||||
err = PTR_ERR(regmap);
|
||||
goto err_clients;
|
||||
}
|
||||
|
||||
at24->nvmem_config.name = dev_name(&client->dev);
|
||||
at24->nvmem_config.dev = &client->dev;
|
||||
at24->nvmem_config.read_only = !writable;
|
||||
at24->nvmem_config.root_only = true;
|
||||
at24->nvmem_config.owner = THIS_MODULE;
|
||||
at24->nvmem_config.compat = true;
|
||||
at24->nvmem_config.base_dev = &client->dev;
|
||||
|
||||
at24->nvmem = nvmem_register(&at24->nvmem_config);
|
||||
|
||||
if (IS_ERR(at24->nvmem)) {
|
||||
err = PTR_ERR(at24->nvmem);
|
||||
goto err_clients;
|
||||
}
|
||||
|
||||
i2c_set_clientdata(client, at24);
|
||||
|
||||
dev_info(&client->dev, "%zu byte %s EEPROM, %s, %u bytes/write\n",
|
||||
at24->bin.size, client->name,
|
||||
dev_info(&client->dev, "%u byte %s EEPROM, %s, %u bytes/write\n",
|
||||
chip.byte_len, client->name,
|
||||
writable ? "writable" : "read-only", at24->write_max);
|
||||
if (use_smbus == I2C_SMBUS_WORD_DATA ||
|
||||
use_smbus == I2C_SMBUS_BYTE_DATA) {
|
||||
|
@ -645,7 +654,7 @@ static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id)
|
|||
|
||||
/* export data to kernel code */
|
||||
if (chip.setup)
|
||||
chip.setup(&at24->macc, chip.context);
|
||||
chip.setup(at24->nvmem, chip.context);
|
||||
|
||||
return 0;
|
||||
|
||||
|
@ -663,7 +672,8 @@ static int at24_remove(struct i2c_client *client)
|
|||
int i;
|
||||
|
||||
at24 = i2c_get_clientdata(client);
|
||||
sysfs_remove_bin_file(&client->dev.kobj, &at24->bin);
|
||||
|
||||
nvmem_unregister(at24->nvmem);
|
||||
|
||||
for (i = 1; i < at24->num_addresses; i++)
|
||||
i2c_unregister_device(at24->client[i]);
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
#include <linux/device.h>
|
||||
#include <linux/sched.h>
|
||||
|
||||
#include <linux/nvmem-provider.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/spi/eeprom.h>
|
||||
#include <linux/property.h>
|
||||
|
@ -29,11 +31,12 @@
|
|||
|
||||
struct at25_data {
|
||||
struct spi_device *spi;
|
||||
struct memory_accessor mem;
|
||||
struct mutex lock;
|
||||
struct spi_eeprom chip;
|
||||
struct bin_attribute bin;
|
||||
unsigned addrlen;
|
||||
struct regmap_config regmap_config;
|
||||
struct nvmem_config nvmem_config;
|
||||
struct nvmem_device *nvmem;
|
||||
};
|
||||
|
||||
#define AT25_WREN 0x06 /* latch the write enable */
|
||||
|
@ -77,10 +80,10 @@ at25_ee_read(
|
|||
struct spi_message m;
|
||||
u8 instr;
|
||||
|
||||
if (unlikely(offset >= at25->bin.size))
|
||||
if (unlikely(offset >= at25->chip.byte_len))
|
||||
return 0;
|
||||
if ((offset + count) > at25->bin.size)
|
||||
count = at25->bin.size - offset;
|
||||
if ((offset + count) > at25->chip.byte_len)
|
||||
count = at25->chip.byte_len - offset;
|
||||
if (unlikely(!count))
|
||||
return count;
|
||||
|
||||
|
@ -131,21 +134,19 @@ at25_ee_read(
|
|||
return status ? status : count;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
at25_bin_read(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr,
|
||||
char *buf, loff_t off, size_t count)
|
||||
static int at25_regmap_read(void *context, const void *reg, size_t reg_size,
|
||||
void *val, size_t val_size)
|
||||
{
|
||||
struct device *dev;
|
||||
struct at25_data *at25;
|
||||
struct at25_data *at25 = context;
|
||||
off_t offset = *(u32 *)reg;
|
||||
int err;
|
||||
|
||||
dev = container_of(kobj, struct device, kobj);
|
||||
at25 = dev_get_drvdata(dev);
|
||||
|
||||
return at25_ee_read(at25, buf, off, count);
|
||||
err = at25_ee_read(at25, val, offset, val_size);
|
||||
if (err)
|
||||
return err;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static ssize_t
|
||||
at25_ee_write(struct at25_data *at25, const char *buf, loff_t off,
|
||||
size_t count)
|
||||
|
@ -155,10 +156,10 @@ at25_ee_write(struct at25_data *at25, const char *buf, loff_t off,
|
|||
unsigned buf_size;
|
||||
u8 *bounce;
|
||||
|
||||
if (unlikely(off >= at25->bin.size))
|
||||
if (unlikely(off >= at25->chip.byte_len))
|
||||
return -EFBIG;
|
||||
if ((off + count) > at25->bin.size)
|
||||
count = at25->bin.size - off;
|
||||
if ((off + count) > at25->chip.byte_len)
|
||||
count = at25->chip.byte_len - off;
|
||||
if (unlikely(!count))
|
||||
return count;
|
||||
|
||||
|
@ -265,39 +266,29 @@ at25_ee_write(struct at25_data *at25, const char *buf, loff_t off,
|
|||
return written ? written : status;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
at25_bin_write(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr,
|
||||
char *buf, loff_t off, size_t count)
|
||||
static int at25_regmap_write(void *context, const void *data, size_t count)
|
||||
{
|
||||
struct device *dev;
|
||||
struct at25_data *at25;
|
||||
struct at25_data *at25 = context;
|
||||
const char *buf;
|
||||
u32 offset;
|
||||
size_t len;
|
||||
int err;
|
||||
|
||||
dev = container_of(kobj, struct device, kobj);
|
||||
at25 = dev_get_drvdata(dev);
|
||||
memcpy(&offset, data, sizeof(offset));
|
||||
buf = (const char *)data + sizeof(offset);
|
||||
len = count - sizeof(offset);
|
||||
|
||||
return at25_ee_write(at25, buf, off, count);
|
||||
err = at25_ee_write(at25, buf, offset, len);
|
||||
if (err)
|
||||
return err;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* Let in-kernel code access the eeprom data. */
|
||||
|
||||
static ssize_t at25_mem_read(struct memory_accessor *mem, char *buf,
|
||||
off_t offset, size_t count)
|
||||
{
|
||||
struct at25_data *at25 = container_of(mem, struct at25_data, mem);
|
||||
|
||||
return at25_ee_read(at25, buf, offset, count);
|
||||
}
|
||||
|
||||
static ssize_t at25_mem_write(struct memory_accessor *mem, const char *buf,
|
||||
off_t offset, size_t count)
|
||||
{
|
||||
struct at25_data *at25 = container_of(mem, struct at25_data, mem);
|
||||
|
||||
return at25_ee_write(at25, buf, offset, count);
|
||||
}
|
||||
static const struct regmap_bus at25_regmap_bus = {
|
||||
.read = at25_regmap_read,
|
||||
.write = at25_regmap_write,
|
||||
.reg_format_endian_default = REGMAP_ENDIAN_NATIVE,
|
||||
};
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
|
@ -358,6 +349,7 @@ static int at25_probe(struct spi_device *spi)
|
|||
{
|
||||
struct at25_data *at25 = NULL;
|
||||
struct spi_eeprom chip;
|
||||
struct regmap *regmap;
|
||||
int err;
|
||||
int sr;
|
||||
int addrlen;
|
||||
|
@ -402,40 +394,35 @@ static int at25_probe(struct spi_device *spi)
|
|||
spi_set_drvdata(spi, at25);
|
||||
at25->addrlen = addrlen;
|
||||
|
||||
/* Export the EEPROM bytes through sysfs, since that's convenient.
|
||||
* And maybe to other kernel code; it might hold a board's Ethernet
|
||||
* address, or board-specific calibration data generated on the
|
||||
* manufacturing floor.
|
||||
*
|
||||
* Default to root-only access to the data; EEPROMs often hold data
|
||||
* that's sensitive for read and/or write, like ethernet addresses,
|
||||
* security codes, board-specific manufacturing calibrations, etc.
|
||||
*/
|
||||
sysfs_bin_attr_init(&at25->bin);
|
||||
at25->bin.attr.name = "eeprom";
|
||||
at25->bin.attr.mode = S_IRUSR;
|
||||
at25->bin.read = at25_bin_read;
|
||||
at25->mem.read = at25_mem_read;
|
||||
at25->regmap_config.reg_bits = 32;
|
||||
at25->regmap_config.val_bits = 8;
|
||||
at25->regmap_config.reg_stride = 1;
|
||||
at25->regmap_config.max_register = chip.byte_len - 1;
|
||||
|
||||
at25->bin.size = at25->chip.byte_len;
|
||||
if (!(chip.flags & EE_READONLY)) {
|
||||
at25->bin.write = at25_bin_write;
|
||||
at25->bin.attr.mode |= S_IWUSR;
|
||||
at25->mem.write = at25_mem_write;
|
||||
regmap = devm_regmap_init(&spi->dev, &at25_regmap_bus, at25,
|
||||
&at25->regmap_config);
|
||||
if (IS_ERR(regmap)) {
|
||||
dev_err(&spi->dev, "regmap init failed\n");
|
||||
return PTR_ERR(regmap);
|
||||
}
|
||||
|
||||
err = sysfs_create_bin_file(&spi->dev.kobj, &at25->bin);
|
||||
if (err)
|
||||
return err;
|
||||
at25->nvmem_config.name = dev_name(&spi->dev);
|
||||
at25->nvmem_config.dev = &spi->dev;
|
||||
at25->nvmem_config.read_only = chip.flags & EE_READONLY;
|
||||
at25->nvmem_config.root_only = true;
|
||||
at25->nvmem_config.owner = THIS_MODULE;
|
||||
at25->nvmem_config.compat = true;
|
||||
at25->nvmem_config.base_dev = &spi->dev;
|
||||
|
||||
if (chip.setup)
|
||||
chip.setup(&at25->mem, chip.context);
|
||||
at25->nvmem = nvmem_register(&at25->nvmem_config);
|
||||
if (IS_ERR(at25->nvmem))
|
||||
return PTR_ERR(at25->nvmem);
|
||||
|
||||
dev_info(&spi->dev, "%Zd %s %s eeprom%s, pagesize %u\n",
|
||||
(at25->bin.size < 1024)
|
||||
? at25->bin.size
|
||||
: (at25->bin.size / 1024),
|
||||
(at25->bin.size < 1024) ? "Byte" : "KByte",
|
||||
dev_info(&spi->dev, "%d %s %s eeprom%s, pagesize %u\n",
|
||||
(chip.byte_len < 1024)
|
||||
? chip.byte_len
|
||||
: (chip.byte_len / 1024),
|
||||
(chip.byte_len < 1024) ? "Byte" : "KByte",
|
||||
at25->chip.name,
|
||||
(chip.flags & EE_READONLY) ? " (readonly)" : "",
|
||||
at25->chip.page_size);
|
||||
|
@ -447,7 +434,8 @@ static int at25_remove(struct spi_device *spi)
|
|||
struct at25_data *at25;
|
||||
|
||||
at25 = spi_get_drvdata(spi);
|
||||
sysfs_remove_bin_file(&spi->dev.kobj, &at25->bin);
|
||||
nvmem_unregister(at25->nvmem);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -84,7 +84,7 @@ static ssize_t eeprom_read(struct file *filp, struct kobject *kobj,
|
|||
struct bin_attribute *bin_attr,
|
||||
char *buf, loff_t off, size_t count)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(container_of(kobj, struct device, kobj));
|
||||
struct i2c_client *client = to_i2c_client(kobj_to_dev(kobj));
|
||||
struct eeprom_data *data = i2c_get_clientdata(client);
|
||||
u8 slice;
|
||||
|
||||
|
|
|
@ -10,12 +10,17 @@
|
|||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/nvmem-provider.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/eeprom_93xx46.h>
|
||||
|
||||
#define OP_START 0x4
|
||||
|
@ -25,73 +30,111 @@
|
|||
#define ADDR_ERAL 0x20
|
||||
#define ADDR_EWEN 0x30
|
||||
|
||||
struct eeprom_93xx46_devtype_data {
|
||||
unsigned int quirks;
|
||||
};
|
||||
|
||||
static const struct eeprom_93xx46_devtype_data atmel_at93c46d_data = {
|
||||
.quirks = EEPROM_93XX46_QUIRK_SINGLE_WORD_READ |
|
||||
EEPROM_93XX46_QUIRK_INSTRUCTION_LENGTH,
|
||||
};
|
||||
|
||||
struct eeprom_93xx46_dev {
|
||||
struct spi_device *spi;
|
||||
struct eeprom_93xx46_platform_data *pdata;
|
||||
struct bin_attribute bin;
|
||||
struct mutex lock;
|
||||
struct regmap_config regmap_config;
|
||||
struct nvmem_config nvmem_config;
|
||||
struct nvmem_device *nvmem;
|
||||
int addrlen;
|
||||
int size;
|
||||
};
|
||||
|
||||
static ssize_t
|
||||
eeprom_93xx46_bin_read(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr,
|
||||
char *buf, loff_t off, size_t count)
|
||||
static inline bool has_quirk_single_word_read(struct eeprom_93xx46_dev *edev)
|
||||
{
|
||||
struct eeprom_93xx46_dev *edev;
|
||||
struct device *dev;
|
||||
struct spi_message m;
|
||||
struct spi_transfer t[2];
|
||||
int bits, ret;
|
||||
u16 cmd_addr;
|
||||
return edev->pdata->quirks & EEPROM_93XX46_QUIRK_SINGLE_WORD_READ;
|
||||
}
|
||||
|
||||
dev = container_of(kobj, struct device, kobj);
|
||||
edev = dev_get_drvdata(dev);
|
||||
static inline bool has_quirk_instruction_length(struct eeprom_93xx46_dev *edev)
|
||||
{
|
||||
return edev->pdata->quirks & EEPROM_93XX46_QUIRK_INSTRUCTION_LENGTH;
|
||||
}
|
||||
|
||||
cmd_addr = OP_READ << edev->addrlen;
|
||||
static ssize_t
|
||||
eeprom_93xx46_read(struct eeprom_93xx46_dev *edev, char *buf,
|
||||
unsigned off, size_t count)
|
||||
{
|
||||
ssize_t ret = 0;
|
||||
|
||||
if (edev->addrlen == 7) {
|
||||
cmd_addr |= off & 0x7f;
|
||||
bits = 10;
|
||||
} else {
|
||||
cmd_addr |= off & 0x3f;
|
||||
bits = 9;
|
||||
}
|
||||
|
||||
dev_dbg(&edev->spi->dev, "read cmd 0x%x, %d Hz\n",
|
||||
cmd_addr, edev->spi->max_speed_hz);
|
||||
|
||||
spi_message_init(&m);
|
||||
memset(t, 0, sizeof(t));
|
||||
|
||||
t[0].tx_buf = (char *)&cmd_addr;
|
||||
t[0].len = 2;
|
||||
t[0].bits_per_word = bits;
|
||||
spi_message_add_tail(&t[0], &m);
|
||||
|
||||
t[1].rx_buf = buf;
|
||||
t[1].len = count;
|
||||
t[1].bits_per_word = 8;
|
||||
spi_message_add_tail(&t[1], &m);
|
||||
if (unlikely(off >= edev->size))
|
||||
return 0;
|
||||
if ((off + count) > edev->size)
|
||||
count = edev->size - off;
|
||||
if (unlikely(!count))
|
||||
return count;
|
||||
|
||||
mutex_lock(&edev->lock);
|
||||
|
||||
if (edev->pdata->prepare)
|
||||
edev->pdata->prepare(edev);
|
||||
|
||||
ret = spi_sync(edev->spi, &m);
|
||||
/* have to wait at least Tcsl ns */
|
||||
ndelay(250);
|
||||
if (ret) {
|
||||
dev_err(&edev->spi->dev, "read %zu bytes at %d: err. %d\n",
|
||||
count, (int)off, ret);
|
||||
while (count) {
|
||||
struct spi_message m;
|
||||
struct spi_transfer t[2] = { { 0 } };
|
||||
u16 cmd_addr = OP_READ << edev->addrlen;
|
||||
size_t nbytes = count;
|
||||
int bits;
|
||||
int err;
|
||||
|
||||
if (edev->addrlen == 7) {
|
||||
cmd_addr |= off & 0x7f;
|
||||
bits = 10;
|
||||
if (has_quirk_single_word_read(edev))
|
||||
nbytes = 1;
|
||||
} else {
|
||||
cmd_addr |= (off >> 1) & 0x3f;
|
||||
bits = 9;
|
||||
if (has_quirk_single_word_read(edev))
|
||||
nbytes = 2;
|
||||
}
|
||||
|
||||
dev_dbg(&edev->spi->dev, "read cmd 0x%x, %d Hz\n",
|
||||
cmd_addr, edev->spi->max_speed_hz);
|
||||
|
||||
spi_message_init(&m);
|
||||
|
||||
t[0].tx_buf = (char *)&cmd_addr;
|
||||
t[0].len = 2;
|
||||
t[0].bits_per_word = bits;
|
||||
spi_message_add_tail(&t[0], &m);
|
||||
|
||||
t[1].rx_buf = buf;
|
||||
t[1].len = count;
|
||||
t[1].bits_per_word = 8;
|
||||
spi_message_add_tail(&t[1], &m);
|
||||
|
||||
err = spi_sync(edev->spi, &m);
|
||||
/* have to wait at least Tcsl ns */
|
||||
ndelay(250);
|
||||
|
||||
if (err) {
|
||||
dev_err(&edev->spi->dev, "read %zu bytes at %d: err. %d\n",
|
||||
nbytes, (int)off, err);
|
||||
ret = err;
|
||||
break;
|
||||
}
|
||||
|
||||
buf += nbytes;
|
||||
off += nbytes;
|
||||
count -= nbytes;
|
||||
ret += nbytes;
|
||||
}
|
||||
|
||||
if (edev->pdata->finish)
|
||||
edev->pdata->finish(edev);
|
||||
|
||||
mutex_unlock(&edev->lock);
|
||||
return ret ? : count;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int eeprom_93xx46_ew(struct eeprom_93xx46_dev *edev, int is_on)
|
||||
|
@ -110,7 +153,13 @@ static int eeprom_93xx46_ew(struct eeprom_93xx46_dev *edev, int is_on)
|
|||
bits = 9;
|
||||
}
|
||||
|
||||
dev_dbg(&edev->spi->dev, "ew cmd 0x%04x\n", cmd_addr);
|
||||
if (has_quirk_instruction_length(edev)) {
|
||||
cmd_addr <<= 2;
|
||||
bits += 2;
|
||||
}
|
||||
|
||||
dev_dbg(&edev->spi->dev, "ew%s cmd 0x%04x, %d bits\n",
|
||||
is_on ? "en" : "ds", cmd_addr, bits);
|
||||
|
||||
spi_message_init(&m);
|
||||
memset(&t, 0, sizeof(t));
|
||||
|
@ -155,7 +204,7 @@ eeprom_93xx46_write_word(struct eeprom_93xx46_dev *edev,
|
|||
bits = 10;
|
||||
data_len = 1;
|
||||
} else {
|
||||
cmd_addr |= off & 0x3f;
|
||||
cmd_addr |= (off >> 1) & 0x3f;
|
||||
bits = 9;
|
||||
data_len = 2;
|
||||
}
|
||||
|
@ -182,16 +231,17 @@ eeprom_93xx46_write_word(struct eeprom_93xx46_dev *edev,
|
|||
}
|
||||
|
||||
static ssize_t
|
||||
eeprom_93xx46_bin_write(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr,
|
||||
char *buf, loff_t off, size_t count)
|
||||
eeprom_93xx46_write(struct eeprom_93xx46_dev *edev, const char *buf,
|
||||
loff_t off, size_t count)
|
||||
{
|
||||
struct eeprom_93xx46_dev *edev;
|
||||
struct device *dev;
|
||||
int i, ret, step = 1;
|
||||
|
||||
dev = container_of(kobj, struct device, kobj);
|
||||
edev = dev_get_drvdata(dev);
|
||||
if (unlikely(off >= edev->size))
|
||||
return -EFBIG;
|
||||
if ((off + count) > edev->size)
|
||||
count = edev->size - off;
|
||||
if (unlikely(!count))
|
||||
return count;
|
||||
|
||||
/* only write even number of bytes on 16-bit devices */
|
||||
if (edev->addrlen == 6) {
|
||||
|
@ -228,6 +278,49 @@ eeprom_93xx46_bin_write(struct file *filp, struct kobject *kobj,
|
|||
return ret ? : count;
|
||||
}
|
||||
|
||||
/*
|
||||
* Provide a regmap interface, which is registered with the NVMEM
|
||||
* framework
|
||||
*/
|
||||
static int eeprom_93xx46_regmap_read(void *context, const void *reg,
|
||||
size_t reg_size, void *val,
|
||||
size_t val_size)
|
||||
{
|
||||
struct eeprom_93xx46_dev *eeprom_93xx46 = context;
|
||||
off_t offset = *(u32 *)reg;
|
||||
int err;
|
||||
|
||||
err = eeprom_93xx46_read(eeprom_93xx46, val, offset, val_size);
|
||||
if (err)
|
||||
return err;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int eeprom_93xx46_regmap_write(void *context, const void *data,
|
||||
size_t count)
|
||||
{
|
||||
struct eeprom_93xx46_dev *eeprom_93xx46 = context;
|
||||
const char *buf;
|
||||
u32 offset;
|
||||
size_t len;
|
||||
int err;
|
||||
|
||||
memcpy(&offset, data, sizeof(offset));
|
||||
buf = (const char *)data + sizeof(offset);
|
||||
len = count - sizeof(offset);
|
||||
|
||||
err = eeprom_93xx46_write(eeprom_93xx46, buf, offset, len);
|
||||
if (err)
|
||||
return err;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct regmap_bus eeprom_93xx46_regmap_bus = {
|
||||
.read = eeprom_93xx46_regmap_read,
|
||||
.write = eeprom_93xx46_regmap_write,
|
||||
.reg_format_endian_default = REGMAP_ENDIAN_NATIVE,
|
||||
};
|
||||
|
||||
static int eeprom_93xx46_eral(struct eeprom_93xx46_dev *edev)
|
||||
{
|
||||
struct eeprom_93xx46_platform_data *pd = edev->pdata;
|
||||
|
@ -245,6 +338,13 @@ static int eeprom_93xx46_eral(struct eeprom_93xx46_dev *edev)
|
|||
bits = 9;
|
||||
}
|
||||
|
||||
if (has_quirk_instruction_length(edev)) {
|
||||
cmd_addr <<= 2;
|
||||
bits += 2;
|
||||
}
|
||||
|
||||
dev_dbg(&edev->spi->dev, "eral cmd 0x%04x, %d bits\n", cmd_addr, bits);
|
||||
|
||||
spi_message_init(&m);
|
||||
memset(&t, 0, sizeof(t));
|
||||
|
||||
|
@ -294,12 +394,101 @@ static ssize_t eeprom_93xx46_store_erase(struct device *dev,
|
|||
}
|
||||
static DEVICE_ATTR(erase, S_IWUSR, NULL, eeprom_93xx46_store_erase);
|
||||
|
||||
static void select_assert(void *context)
|
||||
{
|
||||
struct eeprom_93xx46_dev *edev = context;
|
||||
|
||||
gpiod_set_value_cansleep(edev->pdata->select, 1);
|
||||
}
|
||||
|
||||
static void select_deassert(void *context)
|
||||
{
|
||||
struct eeprom_93xx46_dev *edev = context;
|
||||
|
||||
gpiod_set_value_cansleep(edev->pdata->select, 0);
|
||||
}
|
||||
|
||||
static const struct of_device_id eeprom_93xx46_of_table[] = {
|
||||
{ .compatible = "eeprom-93xx46", },
|
||||
{ .compatible = "atmel,at93c46d", .data = &atmel_at93c46d_data, },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, eeprom_93xx46_of_table);
|
||||
|
||||
static int eeprom_93xx46_probe_dt(struct spi_device *spi)
|
||||
{
|
||||
const struct of_device_id *of_id =
|
||||
of_match_device(eeprom_93xx46_of_table, &spi->dev);
|
||||
struct device_node *np = spi->dev.of_node;
|
||||
struct eeprom_93xx46_platform_data *pd;
|
||||
u32 tmp;
|
||||
int gpio;
|
||||
enum of_gpio_flags of_flags;
|
||||
int ret;
|
||||
|
||||
pd = devm_kzalloc(&spi->dev, sizeof(*pd), GFP_KERNEL);
|
||||
if (!pd)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = of_property_read_u32(np, "data-size", &tmp);
|
||||
if (ret < 0) {
|
||||
dev_err(&spi->dev, "data-size property not found\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (tmp == 8) {
|
||||
pd->flags |= EE_ADDR8;
|
||||
} else if (tmp == 16) {
|
||||
pd->flags |= EE_ADDR16;
|
||||
} else {
|
||||
dev_err(&spi->dev, "invalid data-size (%d)\n", tmp);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (of_property_read_bool(np, "read-only"))
|
||||
pd->flags |= EE_READONLY;
|
||||
|
||||
gpio = of_get_named_gpio_flags(np, "select-gpios", 0, &of_flags);
|
||||
if (gpio_is_valid(gpio)) {
|
||||
unsigned long flags =
|
||||
of_flags == OF_GPIO_ACTIVE_LOW ? GPIOF_ACTIVE_LOW : 0;
|
||||
|
||||
ret = devm_gpio_request_one(&spi->dev, gpio, flags,
|
||||
"eeprom_93xx46_select");
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
pd->select = gpio_to_desc(gpio);
|
||||
pd->prepare = select_assert;
|
||||
pd->finish = select_deassert;
|
||||
|
||||
gpiod_direction_output(pd->select, 0);
|
||||
}
|
||||
|
||||
if (of_id->data) {
|
||||
const struct eeprom_93xx46_devtype_data *data = of_id->data;
|
||||
|
||||
pd->quirks = data->quirks;
|
||||
}
|
||||
|
||||
spi->dev.platform_data = pd;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int eeprom_93xx46_probe(struct spi_device *spi)
|
||||
{
|
||||
struct eeprom_93xx46_platform_data *pd;
|
||||
struct eeprom_93xx46_dev *edev;
|
||||
struct regmap *regmap;
|
||||
int err;
|
||||
|
||||
if (spi->dev.of_node) {
|
||||
err = eeprom_93xx46_probe_dt(spi);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
|
||||
pd = spi->dev.platform_data;
|
||||
if (!pd) {
|
||||
dev_err(&spi->dev, "missing platform data\n");
|
||||
|
@ -325,19 +514,34 @@ static int eeprom_93xx46_probe(struct spi_device *spi)
|
|||
edev->spi = spi_dev_get(spi);
|
||||
edev->pdata = pd;
|
||||
|
||||
sysfs_bin_attr_init(&edev->bin);
|
||||
edev->bin.attr.name = "eeprom";
|
||||
edev->bin.attr.mode = S_IRUSR;
|
||||
edev->bin.read = eeprom_93xx46_bin_read;
|
||||
edev->bin.size = 128;
|
||||
if (!(pd->flags & EE_READONLY)) {
|
||||
edev->bin.write = eeprom_93xx46_bin_write;
|
||||
edev->bin.attr.mode |= S_IWUSR;
|
||||
edev->size = 128;
|
||||
|
||||
edev->regmap_config.reg_bits = 32;
|
||||
edev->regmap_config.val_bits = 8;
|
||||
edev->regmap_config.reg_stride = 1;
|
||||
edev->regmap_config.max_register = edev->size - 1;
|
||||
|
||||
regmap = devm_regmap_init(&spi->dev, &eeprom_93xx46_regmap_bus, edev,
|
||||
&edev->regmap_config);
|
||||
if (IS_ERR(regmap)) {
|
||||
dev_err(&spi->dev, "regmap init failed\n");
|
||||
err = PTR_ERR(regmap);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
err = sysfs_create_bin_file(&spi->dev.kobj, &edev->bin);
|
||||
if (err)
|
||||
edev->nvmem_config.name = dev_name(&spi->dev);
|
||||
edev->nvmem_config.dev = &spi->dev;
|
||||
edev->nvmem_config.read_only = pd->flags & EE_READONLY;
|
||||
edev->nvmem_config.root_only = true;
|
||||
edev->nvmem_config.owner = THIS_MODULE;
|
||||
edev->nvmem_config.compat = true;
|
||||
edev->nvmem_config.base_dev = &spi->dev;
|
||||
|
||||
edev->nvmem = nvmem_register(&edev->nvmem_config);
|
||||
if (IS_ERR(edev->nvmem)) {
|
||||
err = PTR_ERR(edev->nvmem);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
dev_info(&spi->dev, "%d-bit eeprom %s\n",
|
||||
(pd->flags & EE_ADDR8) ? 8 : 16,
|
||||
|
@ -359,10 +563,11 @@ static int eeprom_93xx46_remove(struct spi_device *spi)
|
|||
{
|
||||
struct eeprom_93xx46_dev *edev = spi_get_drvdata(spi);
|
||||
|
||||
nvmem_unregister(edev->nvmem);
|
||||
|
||||
if (!(edev->pdata->flags & EE_READONLY))
|
||||
device_remove_file(&spi->dev, &dev_attr_erase);
|
||||
|
||||
sysfs_remove_bin_file(&spi->dev.kobj, &edev->bin);
|
||||
kfree(edev);
|
||||
return 0;
|
||||
}
|
||||
|
@ -370,6 +575,7 @@ static int eeprom_93xx46_remove(struct spi_device *spi)
|
|||
static struct spi_driver eeprom_93xx46_driver = {
|
||||
.driver = {
|
||||
.name = "93xx46",
|
||||
.of_match_table = of_match_ptr(eeprom_93xx46_of_table),
|
||||
},
|
||||
.probe = eeprom_93xx46_probe,
|
||||
.remove = eeprom_93xx46_remove,
|
||||
|
|
|
@ -278,7 +278,7 @@ static umode_t genwqe_is_visible(struct kobject *kobj,
|
|||
struct attribute *attr, int n)
|
||||
{
|
||||
unsigned int j;
|
||||
struct device *dev = container_of(kobj, struct device, kobj);
|
||||
struct device *dev = kobj_to_dev(kobj);
|
||||
struct genwqe_dev *cd = dev_get_drvdata(dev);
|
||||
umode_t mode = attr->mode;
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
#include <linux/kref.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/time64.h>
|
||||
|
||||
/* Driver identification */
|
||||
#define DRIVER_NAME "ibmasm"
|
||||
|
@ -53,9 +54,11 @@ extern int ibmasm_debug;
|
|||
|
||||
static inline char *get_timestamp(char *buf)
|
||||
{
|
||||
struct timeval now;
|
||||
do_gettimeofday(&now);
|
||||
sprintf(buf, "%lu.%lu", now.tv_sec, now.tv_usec);
|
||||
struct timespec64 now;
|
||||
|
||||
ktime_get_real_ts64(&now);
|
||||
sprintf(buf, "%llu.%.08lu", (long long)now.tv_sec,
|
||||
now.tv_nsec / NSEC_PER_USEC);
|
||||
return buf;
|
||||
}
|
||||
|
||||
|
|
|
@ -209,7 +209,7 @@ static int lis3lv02d_i2c_remove(struct i2c_client *client)
|
|||
#ifdef CONFIG_PM_SLEEP
|
||||
static int lis3lv02d_i2c_suspend(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = container_of(dev, struct i2c_client, dev);
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct lis3lv02d *lis3 = i2c_get_clientdata(client);
|
||||
|
||||
if (!lis3->pdata || !lis3->pdata->wakeup_flags)
|
||||
|
@ -219,7 +219,7 @@ static int lis3lv02d_i2c_suspend(struct device *dev)
|
|||
|
||||
static int lis3lv02d_i2c_resume(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = container_of(dev, struct i2c_client, dev);
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct lis3lv02d *lis3 = i2c_get_clientdata(client);
|
||||
|
||||
/*
|
||||
|
@ -238,7 +238,7 @@ static int lis3lv02d_i2c_resume(struct device *dev)
|
|||
#ifdef CONFIG_PM
|
||||
static int lis3_i2c_runtime_suspend(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = container_of(dev, struct i2c_client, dev);
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct lis3lv02d *lis3 = i2c_get_clientdata(client);
|
||||
|
||||
lis3lv02d_poweroff(lis3);
|
||||
|
@ -247,7 +247,7 @@ static int lis3_i2c_runtime_suspend(struct device *dev)
|
|||
|
||||
static int lis3_i2c_runtime_resume(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = container_of(dev, struct i2c_client, dev);
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct lis3lv02d *lis3 = i2c_get_clientdata(client);
|
||||
|
||||
lis3lv02d_poweron(lis3);
|
||||
|
|
|
@ -92,6 +92,9 @@ enum ctype {
|
|||
CT_UNALIGNED_LOAD_STORE_WRITE,
|
||||
CT_OVERWRITE_ALLOCATION,
|
||||
CT_WRITE_AFTER_FREE,
|
||||
CT_READ_AFTER_FREE,
|
||||
CT_WRITE_BUDDY_AFTER_FREE,
|
||||
CT_READ_BUDDY_AFTER_FREE,
|
||||
CT_SOFTLOCKUP,
|
||||
CT_HARDLOCKUP,
|
||||
CT_SPINLOCKUP,
|
||||
|
@ -105,6 +108,7 @@ enum ctype {
|
|||
CT_WRITE_RO,
|
||||
CT_WRITE_RO_AFTER_INIT,
|
||||
CT_WRITE_KERN,
|
||||
CT_WRAP_ATOMIC
|
||||
};
|
||||
|
||||
static char* cp_name[] = {
|
||||
|
@ -130,6 +134,9 @@ static char* cp_type[] = {
|
|||
"UNALIGNED_LOAD_STORE_WRITE",
|
||||
"OVERWRITE_ALLOCATION",
|
||||
"WRITE_AFTER_FREE",
|
||||
"READ_AFTER_FREE",
|
||||
"WRITE_BUDDY_AFTER_FREE",
|
||||
"READ_BUDDY_AFTER_FREE",
|
||||
"SOFTLOCKUP",
|
||||
"HARDLOCKUP",
|
||||
"SPINLOCKUP",
|
||||
|
@ -143,6 +150,7 @@ static char* cp_type[] = {
|
|||
"WRITE_RO",
|
||||
"WRITE_RO_AFTER_INIT",
|
||||
"WRITE_KERN",
|
||||
"WRAP_ATOMIC"
|
||||
};
|
||||
|
||||
static struct jprobe lkdtm;
|
||||
|
@ -338,7 +346,7 @@ static noinline void corrupt_stack(void)
|
|||
memset((void *)data, 0, 64);
|
||||
}
|
||||
|
||||
static void execute_location(void *dst)
|
||||
static void noinline execute_location(void *dst)
|
||||
{
|
||||
void (*func)(void) = dst;
|
||||
|
||||
|
@ -412,12 +420,109 @@ static void lkdtm_do_action(enum ctype which)
|
|||
break;
|
||||
}
|
||||
case CT_WRITE_AFTER_FREE: {
|
||||
int *base, *again;
|
||||
size_t len = 1024;
|
||||
u32 *data = kmalloc(len, GFP_KERNEL);
|
||||
/*
|
||||
* The slub allocator uses the first word to store the free
|
||||
* pointer in some configurations. Use the middle of the
|
||||
* allocation to avoid running into the freelist
|
||||
*/
|
||||
size_t offset = (len / sizeof(*base)) / 2;
|
||||
|
||||
kfree(data);
|
||||
base = kmalloc(len, GFP_KERNEL);
|
||||
pr_info("Allocated memory %p-%p\n", base, &base[offset * 2]);
|
||||
pr_info("Attempting bad write to freed memory at %p\n",
|
||||
&base[offset]);
|
||||
kfree(base);
|
||||
base[offset] = 0x0abcdef0;
|
||||
/* Attempt to notice the overwrite. */
|
||||
again = kmalloc(len, GFP_KERNEL);
|
||||
kfree(again);
|
||||
if (again != base)
|
||||
pr_info("Hmm, didn't get the same memory range.\n");
|
||||
|
||||
break;
|
||||
}
|
||||
case CT_READ_AFTER_FREE: {
|
||||
int *base, *val, saw;
|
||||
size_t len = 1024;
|
||||
/*
|
||||
* The slub allocator uses the first word to store the free
|
||||
* pointer in some configurations. Use the middle of the
|
||||
* allocation to avoid running into the freelist
|
||||
*/
|
||||
size_t offset = (len / sizeof(*base)) / 2;
|
||||
|
||||
base = kmalloc(len, GFP_KERNEL);
|
||||
if (!base)
|
||||
break;
|
||||
|
||||
val = kmalloc(len, GFP_KERNEL);
|
||||
if (!val)
|
||||
break;
|
||||
|
||||
*val = 0x12345678;
|
||||
base[offset] = *val;
|
||||
pr_info("Value in memory before free: %x\n", base[offset]);
|
||||
|
||||
kfree(base);
|
||||
|
||||
pr_info("Attempting bad read from freed memory\n");
|
||||
saw = base[offset];
|
||||
if (saw != *val) {
|
||||
/* Good! Poisoning happened, so declare a win. */
|
||||
pr_info("Memory correctly poisoned (%x)\n", saw);
|
||||
BUG();
|
||||
}
|
||||
pr_info("Memory was not poisoned\n");
|
||||
|
||||
kfree(val);
|
||||
break;
|
||||
}
|
||||
case CT_WRITE_BUDDY_AFTER_FREE: {
|
||||
unsigned long p = __get_free_page(GFP_KERNEL);
|
||||
if (!p)
|
||||
break;
|
||||
pr_info("Writing to the buddy page before free\n");
|
||||
memset((void *)p, 0x3, PAGE_SIZE);
|
||||
free_page(p);
|
||||
schedule();
|
||||
memset(data, 0x78, len);
|
||||
pr_info("Attempting bad write to the buddy page after free\n");
|
||||
memset((void *)p, 0x78, PAGE_SIZE);
|
||||
/* Attempt to notice the overwrite. */
|
||||
p = __get_free_page(GFP_KERNEL);
|
||||
free_page(p);
|
||||
schedule();
|
||||
|
||||
break;
|
||||
}
|
||||
case CT_READ_BUDDY_AFTER_FREE: {
|
||||
unsigned long p = __get_free_page(GFP_KERNEL);
|
||||
int saw, *val = kmalloc(1024, GFP_KERNEL);
|
||||
int *base;
|
||||
|
||||
if (!p)
|
||||
break;
|
||||
|
||||
if (!val)
|
||||
break;
|
||||
|
||||
base = (int *)p;
|
||||
|
||||
*val = 0x12345678;
|
||||
base[0] = *val;
|
||||
pr_info("Value in memory before free: %x\n", base[0]);
|
||||
free_page(p);
|
||||
pr_info("Attempting to read from freed memory\n");
|
||||
saw = base[0];
|
||||
if (saw != *val) {
|
||||
/* Good! Poisoning happened, so declare a win. */
|
||||
pr_info("Memory correctly poisoned (%x)\n", saw);
|
||||
BUG();
|
||||
}
|
||||
pr_info("Buddy page was not poisoned\n");
|
||||
|
||||
kfree(val);
|
||||
break;
|
||||
}
|
||||
case CT_SOFTLOCKUP:
|
||||
|
@ -548,6 +653,17 @@ static void lkdtm_do_action(enum ctype which)
|
|||
do_overwritten();
|
||||
break;
|
||||
}
|
||||
case CT_WRAP_ATOMIC: {
|
||||
atomic_t under = ATOMIC_INIT(INT_MIN);
|
||||
atomic_t over = ATOMIC_INIT(INT_MAX);
|
||||
|
||||
pr_info("attempting atomic underflow\n");
|
||||
atomic_dec(&under);
|
||||
pr_info("attempting atomic overflow\n");
|
||||
atomic_inc(&over);
|
||||
|
||||
return;
|
||||
}
|
||||
case CT_NONE:
|
||||
default:
|
||||
break;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
config INTEL_MEI
|
||||
tristate "Intel Management Engine Interface"
|
||||
depends on X86 && PCI && WATCHDOG_CORE
|
||||
depends on X86 && PCI
|
||||
help
|
||||
The Intel Management Engine (Intel ME) provides Manageability,
|
||||
Security and Media services for system containing Intel chipsets.
|
||||
|
@ -12,7 +12,7 @@ config INTEL_MEI
|
|||
config INTEL_MEI_ME
|
||||
tristate "ME Enabled Intel Chipsets"
|
||||
select INTEL_MEI
|
||||
depends on X86 && PCI && WATCHDOG_CORE
|
||||
depends on X86 && PCI
|
||||
help
|
||||
MEI support for ME Enabled Intel chipsets.
|
||||
|
||||
|
@ -37,7 +37,7 @@ config INTEL_MEI_ME
|
|||
config INTEL_MEI_TXE
|
||||
tristate "Intel Trusted Execution Environment with ME Interface"
|
||||
select INTEL_MEI
|
||||
depends on X86 && PCI && WATCHDOG_CORE
|
||||
depends on X86 && PCI
|
||||
help
|
||||
MEI Support for Trusted Execution Environment device on Intel SoCs
|
||||
|
||||
|
|
|
@ -9,7 +9,6 @@ mei-objs += interrupt.o
|
|||
mei-objs += client.o
|
||||
mei-objs += main.o
|
||||
mei-objs += amthif.o
|
||||
mei-objs += wd.o
|
||||
mei-objs += bus.o
|
||||
mei-objs += bus-fixup.o
|
||||
mei-$(CONFIG_DEBUG_FS) += debugfs.o
|
||||
|
|
|
@ -50,7 +50,6 @@ void mei_amthif_reset_params(struct mei_device *dev)
|
|||
dev->iamthif_current_cb = NULL;
|
||||
dev->iamthif_canceled = false;
|
||||
dev->iamthif_state = MEI_IAMTHIF_IDLE;
|
||||
dev->iamthif_timer = 0;
|
||||
dev->iamthif_stall_timer = 0;
|
||||
dev->iamthif_open_count = 0;
|
||||
}
|
||||
|
@ -68,11 +67,14 @@ int mei_amthif_host_init(struct mei_device *dev, struct mei_me_client *me_cl)
|
|||
struct mei_cl *cl = &dev->iamthif_cl;
|
||||
int ret;
|
||||
|
||||
if (mei_cl_is_connected(cl))
|
||||
return 0;
|
||||
|
||||
dev->iamthif_state = MEI_IAMTHIF_IDLE;
|
||||
|
||||
mei_cl_init(cl, dev);
|
||||
|
||||
ret = mei_cl_link(cl, MEI_IAMTHIF_HOST_CLIENT_ID);
|
||||
ret = mei_cl_link(cl);
|
||||
if (ret < 0) {
|
||||
dev_err(dev->dev, "amthif: failed cl_link %d\n", ret);
|
||||
return ret;
|
||||
|
@ -80,31 +82,9 @@ int mei_amthif_host_init(struct mei_device *dev, struct mei_me_client *me_cl)
|
|||
|
||||
ret = mei_cl_connect(cl, me_cl, NULL);
|
||||
|
||||
dev->iamthif_state = MEI_IAMTHIF_IDLE;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* mei_amthif_find_read_list_entry - finds a amthilist entry for current file
|
||||
*
|
||||
* @dev: the device structure
|
||||
* @file: pointer to file object
|
||||
*
|
||||
* Return: returned a list entry on success, NULL on failure.
|
||||
*/
|
||||
struct mei_cl_cb *mei_amthif_find_read_list_entry(struct mei_device *dev,
|
||||
struct file *file)
|
||||
{
|
||||
struct mei_cl_cb *cb;
|
||||
|
||||
list_for_each_entry(cb, &dev->amthif_rd_complete_list.list, list)
|
||||
if (cb->file_object == file)
|
||||
return cb;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* mei_amthif_read - read data from AMTHIF client
|
||||
*
|
||||
|
@ -126,18 +106,11 @@ int mei_amthif_read(struct mei_device *dev, struct file *file,
|
|||
{
|
||||
struct mei_cl *cl = file->private_data;
|
||||
struct mei_cl_cb *cb;
|
||||
unsigned long timeout;
|
||||
int rets;
|
||||
int wait_ret;
|
||||
|
||||
/* Only possible if we are in timeout */
|
||||
if (!cl) {
|
||||
dev_err(dev->dev, "bad file ext.\n");
|
||||
return -ETIME;
|
||||
}
|
||||
|
||||
dev_dbg(dev->dev, "checking amthif data\n");
|
||||
cb = mei_amthif_find_read_list_entry(dev, file);
|
||||
cb = mei_cl_read_cb(cl, file);
|
||||
|
||||
/* Check for if we can block or not*/
|
||||
if (cb == NULL && file->f_flags & O_NONBLOCK)
|
||||
|
@ -149,8 +122,9 @@ int mei_amthif_read(struct mei_device *dev, struct file *file,
|
|||
/* unlock the Mutex */
|
||||
mutex_unlock(&dev->device_lock);
|
||||
|
||||
wait_ret = wait_event_interruptible(dev->iamthif_cl.wait,
|
||||
(cb = mei_amthif_find_read_list_entry(dev, file)));
|
||||
wait_ret = wait_event_interruptible(cl->rx_wait,
|
||||
!list_empty(&cl->rd_completed) ||
|
||||
!mei_cl_is_connected(cl));
|
||||
|
||||
/* Locking again the Mutex */
|
||||
mutex_lock(&dev->device_lock);
|
||||
|
@ -158,7 +132,12 @@ int mei_amthif_read(struct mei_device *dev, struct file *file,
|
|||
if (wait_ret)
|
||||
return -ERESTARTSYS;
|
||||
|
||||
dev_dbg(dev->dev, "woke up from sleep\n");
|
||||
if (!mei_cl_is_connected(cl)) {
|
||||
rets = -EBUSY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
cb = mei_cl_read_cb(cl, file);
|
||||
}
|
||||
|
||||
if (cb->status) {
|
||||
|
@ -168,24 +147,10 @@ int mei_amthif_read(struct mei_device *dev, struct file *file,
|
|||
}
|
||||
|
||||
dev_dbg(dev->dev, "Got amthif data\n");
|
||||
dev->iamthif_timer = 0;
|
||||
|
||||
timeout = cb->read_time +
|
||||
mei_secs_to_jiffies(MEI_IAMTHIF_READ_TIMER);
|
||||
dev_dbg(dev->dev, "amthif timeout = %lud\n",
|
||||
timeout);
|
||||
|
||||
if (time_after(jiffies, timeout)) {
|
||||
dev_dbg(dev->dev, "amthif Time out\n");
|
||||
/* 15 sec for the message has expired */
|
||||
list_del_init(&cb->list);
|
||||
rets = -ETIME;
|
||||
goto free;
|
||||
}
|
||||
/* if the whole message will fit remove it from the list */
|
||||
if (cb->buf_idx >= *offset && length >= (cb->buf_idx - *offset))
|
||||
list_del_init(&cb->list);
|
||||
else if (cb->buf_idx > 0 && cb->buf_idx <= *offset) {
|
||||
else if (cb->buf_idx <= *offset) {
|
||||
/* end of the message has been reached */
|
||||
list_del_init(&cb->list);
|
||||
rets = 0;
|
||||
|
@ -195,9 +160,8 @@ int mei_amthif_read(struct mei_device *dev, struct file *file,
|
|||
* remove message from deletion list
|
||||
*/
|
||||
|
||||
dev_dbg(dev->dev, "amthif cb->buf size - %d\n",
|
||||
cb->buf.size);
|
||||
dev_dbg(dev->dev, "amthif cb->buf_idx - %lu\n", cb->buf_idx);
|
||||
dev_dbg(dev->dev, "amthif cb->buf.size - %zu cb->buf_idx - %zu\n",
|
||||
cb->buf.size, cb->buf_idx);
|
||||
|
||||
/* length is being truncated to PAGE_SIZE, however,
|
||||
* the buf_idx may point beyond */
|
||||
|
@ -229,7 +193,7 @@ out:
|
|||
*
|
||||
* Return: 0 on success, <0 on failure.
|
||||
*/
|
||||
static int mei_amthif_read_start(struct mei_cl *cl, struct file *file)
|
||||
static int mei_amthif_read_start(struct mei_cl *cl, const struct file *file)
|
||||
{
|
||||
struct mei_device *dev = cl->dev;
|
||||
struct mei_cl_cb *cb;
|
||||
|
@ -248,7 +212,7 @@ static int mei_amthif_read_start(struct mei_cl *cl, struct file *file)
|
|||
list_add_tail(&cb->list, &dev->ctrl_wr_list.list);
|
||||
|
||||
dev->iamthif_state = MEI_IAMTHIF_READING;
|
||||
dev->iamthif_file_object = cb->file_object;
|
||||
dev->iamthif_fp = cb->fp;
|
||||
dev->iamthif_current_cb = cb;
|
||||
|
||||
return 0;
|
||||
|
@ -277,7 +241,7 @@ static int mei_amthif_send_cmd(struct mei_cl *cl, struct mei_cl_cb *cb)
|
|||
|
||||
dev->iamthif_state = MEI_IAMTHIF_WRITING;
|
||||
dev->iamthif_current_cb = cb;
|
||||
dev->iamthif_file_object = cb->file_object;
|
||||
dev->iamthif_fp = cb->fp;
|
||||
dev->iamthif_canceled = false;
|
||||
|
||||
ret = mei_cl_write(cl, cb, false);
|
||||
|
@ -285,7 +249,7 @@ static int mei_amthif_send_cmd(struct mei_cl *cl, struct mei_cl_cb *cb)
|
|||
return ret;
|
||||
|
||||
if (cb->completed)
|
||||
cb->status = mei_amthif_read_start(cl, cb->file_object);
|
||||
cb->status = mei_amthif_read_start(cl, cb->fp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -304,8 +268,7 @@ int mei_amthif_run_next_cmd(struct mei_device *dev)
|
|||
|
||||
dev->iamthif_canceled = false;
|
||||
dev->iamthif_state = MEI_IAMTHIF_IDLE;
|
||||
dev->iamthif_timer = 0;
|
||||
dev->iamthif_file_object = NULL;
|
||||
dev->iamthif_fp = NULL;
|
||||
|
||||
dev_dbg(dev->dev, "complete amthif cmd_list cb.\n");
|
||||
|
||||
|
@ -329,17 +292,17 @@ int mei_amthif_run_next_cmd(struct mei_device *dev)
|
|||
int mei_amthif_write(struct mei_cl *cl, struct mei_cl_cb *cb)
|
||||
{
|
||||
|
||||
struct mei_device *dev;
|
||||
|
||||
if (WARN_ON(!cl || !cl->dev))
|
||||
return -ENODEV;
|
||||
|
||||
if (WARN_ON(!cb))
|
||||
return -EINVAL;
|
||||
|
||||
dev = cl->dev;
|
||||
struct mei_device *dev = cl->dev;
|
||||
|
||||
list_add_tail(&cb->list, &dev->amthif_cmd_list.list);
|
||||
|
||||
/*
|
||||
* The previous request is still in processing, queue this one.
|
||||
*/
|
||||
if (dev->iamthif_state > MEI_IAMTHIF_IDLE &&
|
||||
dev->iamthif_state < MEI_IAMTHIF_READ_COMPLETE)
|
||||
return 0;
|
||||
|
||||
return mei_amthif_run_next_cmd(dev);
|
||||
}
|
||||
|
||||
|
@ -360,10 +323,10 @@ unsigned int mei_amthif_poll(struct mei_device *dev,
|
|||
{
|
||||
unsigned int mask = 0;
|
||||
|
||||
poll_wait(file, &dev->iamthif_cl.wait, wait);
|
||||
poll_wait(file, &dev->iamthif_cl.rx_wait, wait);
|
||||
|
||||
if (dev->iamthif_state == MEI_IAMTHIF_READ_COMPLETE &&
|
||||
dev->iamthif_file_object == file) {
|
||||
dev->iamthif_fp == file) {
|
||||
|
||||
mask |= POLLIN | POLLRDNORM;
|
||||
mei_amthif_run_next_cmd(dev);
|
||||
|
@ -393,7 +356,7 @@ int mei_amthif_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb,
|
|||
return ret;
|
||||
|
||||
if (cb->completed)
|
||||
cb->status = mei_amthif_read_start(cl, cb->file_object);
|
||||
cb->status = mei_amthif_read_start(cl, cb->fp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -437,11 +400,12 @@ int mei_amthif_irq_read_msg(struct mei_cl *cl,
|
|||
/**
|
||||
* mei_amthif_complete - complete amthif callback.
|
||||
*
|
||||
* @dev: the device structure.
|
||||
* @cl: host client
|
||||
* @cb: callback block.
|
||||
*/
|
||||
void mei_amthif_complete(struct mei_device *dev, struct mei_cl_cb *cb)
|
||||
void mei_amthif_complete(struct mei_cl *cl, struct mei_cl_cb *cb)
|
||||
{
|
||||
struct mei_device *dev = cl->dev;
|
||||
|
||||
if (cb->fop_type == MEI_FOP_WRITE) {
|
||||
if (!cb->status) {
|
||||
|
@ -453,25 +417,22 @@ void mei_amthif_complete(struct mei_device *dev, struct mei_cl_cb *cb)
|
|||
* in case of error enqueue the write cb to complete read list
|
||||
* so it can be propagated to the reader
|
||||
*/
|
||||
list_add_tail(&cb->list, &dev->amthif_rd_complete_list.list);
|
||||
wake_up_interruptible(&dev->iamthif_cl.wait);
|
||||
list_add_tail(&cb->list, &cl->rd_completed);
|
||||
wake_up_interruptible(&cl->rx_wait);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!dev->iamthif_canceled) {
|
||||
dev->iamthif_state = MEI_IAMTHIF_READ_COMPLETE;
|
||||
dev->iamthif_stall_timer = 0;
|
||||
list_add_tail(&cb->list, &dev->amthif_rd_complete_list.list);
|
||||
list_add_tail(&cb->list, &cl->rd_completed);
|
||||
dev_dbg(dev->dev, "amthif read completed\n");
|
||||
dev->iamthif_timer = jiffies;
|
||||
dev_dbg(dev->dev, "dev->iamthif_timer = %ld\n",
|
||||
dev->iamthif_timer);
|
||||
} else {
|
||||
mei_amthif_run_next_cmd(dev);
|
||||
}
|
||||
|
||||
dev_dbg(dev->dev, "completing amthif call back.\n");
|
||||
wake_up_interruptible(&dev->iamthif_cl.wait);
|
||||
wake_up_interruptible(&cl->rx_wait);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -497,7 +458,7 @@ static bool mei_clear_list(struct mei_device *dev,
|
|||
/* list all list member */
|
||||
list_for_each_entry_safe(cb, next, mei_cb_list, list) {
|
||||
/* check if list member associated with a file */
|
||||
if (file == cb->file_object) {
|
||||
if (file == cb->fp) {
|
||||
/* check if cb equal to current iamthif cb */
|
||||
if (dev->iamthif_current_cb == cb) {
|
||||
dev->iamthif_current_cb = NULL;
|
||||
|
@ -523,13 +484,14 @@ static bool mei_clear_list(struct mei_device *dev,
|
|||
*
|
||||
* Return: true if callback removed from the list, false otherwise
|
||||
*/
|
||||
static bool mei_clear_lists(struct mei_device *dev, struct file *file)
|
||||
static bool mei_clear_lists(struct mei_device *dev, const struct file *file)
|
||||
{
|
||||
bool removed = false;
|
||||
struct mei_cl *cl = &dev->iamthif_cl;
|
||||
|
||||
/* remove callbacks associated with a file */
|
||||
mei_clear_list(dev, file, &dev->amthif_cmd_list.list);
|
||||
if (mei_clear_list(dev, file, &dev->amthif_rd_complete_list.list))
|
||||
if (mei_clear_list(dev, file, &cl->rd_completed))
|
||||
removed = true;
|
||||
|
||||
mei_clear_list(dev, file, &dev->ctrl_rd_list.list);
|
||||
|
@ -546,7 +508,7 @@ static bool mei_clear_lists(struct mei_device *dev, struct file *file)
|
|||
/* check if iamthif_current_cb not NULL */
|
||||
if (dev->iamthif_current_cb && !removed) {
|
||||
/* check file and iamthif current cb association */
|
||||
if (dev->iamthif_current_cb->file_object == file) {
|
||||
if (dev->iamthif_current_cb->fp == file) {
|
||||
/* remove cb */
|
||||
mei_io_cb_free(dev->iamthif_current_cb);
|
||||
dev->iamthif_current_cb = NULL;
|
||||
|
@ -569,7 +531,7 @@ int mei_amthif_release(struct mei_device *dev, struct file *file)
|
|||
if (dev->iamthif_open_count > 0)
|
||||
dev->iamthif_open_count--;
|
||||
|
||||
if (dev->iamthif_file_object == file &&
|
||||
if (dev->iamthif_fp == file &&
|
||||
dev->iamthif_state != MEI_IAMTHIF_IDLE) {
|
||||
|
||||
dev_dbg(dev->dev, "amthif canceled iamthif state %d\n",
|
||||
|
|
|
@ -35,6 +35,9 @@ static const uuid_le mei_nfc_info_guid = MEI_UUID_NFC_INFO;
|
|||
#define MEI_UUID_NFC_HCI UUID_LE(0x0bb17a78, 0x2a8e, 0x4c50, \
|
||||
0x94, 0xd4, 0x50, 0x26, 0x67, 0x23, 0x77, 0x5c)
|
||||
|
||||
#define MEI_UUID_WD UUID_LE(0x05B79A6F, 0x4628, 0x4D7F, \
|
||||
0x89, 0x9D, 0xA9, 0x15, 0x14, 0xCB, 0x32, 0xAB)
|
||||
|
||||
#define MEI_UUID_ANY NULL_UUID_LE
|
||||
|
||||
/**
|
||||
|
@ -48,8 +51,7 @@ static const uuid_le mei_nfc_info_guid = MEI_UUID_NFC_INFO;
|
|||
*/
|
||||
static void number_of_connections(struct mei_cl_device *cldev)
|
||||
{
|
||||
dev_dbg(&cldev->dev, "running hook %s on %pUl\n",
|
||||
__func__, mei_me_cl_uuid(cldev->me_cl));
|
||||
dev_dbg(&cldev->dev, "running hook %s\n", __func__);
|
||||
|
||||
if (cldev->me_cl->props.max_number_of_connections > 1)
|
||||
cldev->do_match = 0;
|
||||
|
@ -62,11 +64,36 @@ static void number_of_connections(struct mei_cl_device *cldev)
|
|||
*/
|
||||
static void blacklist(struct mei_cl_device *cldev)
|
||||
{
|
||||
dev_dbg(&cldev->dev, "running hook %s on %pUl\n",
|
||||
__func__, mei_me_cl_uuid(cldev->me_cl));
|
||||
dev_dbg(&cldev->dev, "running hook %s\n", __func__);
|
||||
|
||||
cldev->do_match = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* mei_wd - wd client on the bus, change protocol version
|
||||
* as the API has changed.
|
||||
*
|
||||
* @cldev: me clients device
|
||||
*/
|
||||
#if IS_ENABLED(CONFIG_INTEL_MEI_ME)
|
||||
#include <linux/pci.h>
|
||||
#include "hw-me-regs.h"
|
||||
static void mei_wd(struct mei_cl_device *cldev)
|
||||
{
|
||||
struct pci_dev *pdev = to_pci_dev(cldev->dev.parent);
|
||||
|
||||
dev_dbg(&cldev->dev, "running hook %s\n", __func__);
|
||||
if (pdev->device == MEI_DEV_ID_WPT_LP ||
|
||||
pdev->device == MEI_DEV_ID_SPT ||
|
||||
pdev->device == MEI_DEV_ID_SPT_H)
|
||||
cldev->me_cl->props.protocol_version = 0x2;
|
||||
|
||||
cldev->do_match = 1;
|
||||
}
|
||||
#else
|
||||
static inline void mei_wd(struct mei_cl_device *cldev) {}
|
||||
#endif /* CONFIG_INTEL_MEI_ME */
|
||||
|
||||
struct mei_nfc_cmd {
|
||||
u8 command;
|
||||
u8 status;
|
||||
|
@ -208,12 +235,11 @@ static void mei_nfc(struct mei_cl_device *cldev)
|
|||
|
||||
bus = cldev->bus;
|
||||
|
||||
dev_dbg(bus->dev, "running hook %s: %pUl match=%d\n",
|
||||
__func__, mei_me_cl_uuid(cldev->me_cl), cldev->do_match);
|
||||
dev_dbg(&cldev->dev, "running hook %s\n", __func__);
|
||||
|
||||
mutex_lock(&bus->device_lock);
|
||||
/* we need to connect to INFO GUID */
|
||||
cl = mei_cl_alloc_linked(bus, MEI_HOST_CLIENT_ID_ANY);
|
||||
cl = mei_cl_alloc_linked(bus);
|
||||
if (IS_ERR(cl)) {
|
||||
ret = PTR_ERR(cl);
|
||||
cl = NULL;
|
||||
|
@ -282,6 +308,7 @@ static struct mei_fixup {
|
|||
MEI_FIXUP(MEI_UUID_ANY, number_of_connections),
|
||||
MEI_FIXUP(MEI_UUID_NFC_INFO, blacklist),
|
||||
MEI_FIXUP(MEI_UUID_NFC_HCI, mei_nfc),
|
||||
MEI_FIXUP(MEI_UUID_WD, mei_wd),
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -44,7 +44,7 @@ ssize_t __mei_cl_send(struct mei_cl *cl, u8 *buf, size_t length,
|
|||
bool blocking)
|
||||
{
|
||||
struct mei_device *bus;
|
||||
struct mei_cl_cb *cb = NULL;
|
||||
struct mei_cl_cb *cb;
|
||||
ssize_t rets;
|
||||
|
||||
if (WARN_ON(!cl || !cl->dev))
|
||||
|
@ -53,6 +53,11 @@ ssize_t __mei_cl_send(struct mei_cl *cl, u8 *buf, size_t length,
|
|||
bus = cl->dev;
|
||||
|
||||
mutex_lock(&bus->device_lock);
|
||||
if (bus->dev_state != MEI_DEV_ENABLED) {
|
||||
rets = -ENODEV;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!mei_cl_is_connected(cl)) {
|
||||
rets = -ENODEV;
|
||||
goto out;
|
||||
|
@ -81,8 +86,6 @@ ssize_t __mei_cl_send(struct mei_cl *cl, u8 *buf, size_t length,
|
|||
|
||||
out:
|
||||
mutex_unlock(&bus->device_lock);
|
||||
if (rets < 0)
|
||||
mei_io_cb_free(cb);
|
||||
|
||||
return rets;
|
||||
}
|
||||
|
@ -109,6 +112,10 @@ ssize_t __mei_cl_recv(struct mei_cl *cl, u8 *buf, size_t length)
|
|||
bus = cl->dev;
|
||||
|
||||
mutex_lock(&bus->device_lock);
|
||||
if (bus->dev_state != MEI_DEV_ENABLED) {
|
||||
rets = -ENODEV;
|
||||
goto out;
|
||||
}
|
||||
|
||||
cb = mei_cl_read_cb(cl, NULL);
|
||||
if (cb)
|
||||
|
@ -230,45 +237,55 @@ static void mei_cl_bus_event_work(struct work_struct *work)
|
|||
* mei_cl_bus_notify_event - schedule notify cb on bus client
|
||||
*
|
||||
* @cl: host client
|
||||
*
|
||||
* Return: true if event was scheduled
|
||||
* false if the client is not waiting for event
|
||||
*/
|
||||
void mei_cl_bus_notify_event(struct mei_cl *cl)
|
||||
bool mei_cl_bus_notify_event(struct mei_cl *cl)
|
||||
{
|
||||
struct mei_cl_device *cldev = cl->cldev;
|
||||
|
||||
if (!cldev || !cldev->event_cb)
|
||||
return;
|
||||
return false;
|
||||
|
||||
if (!(cldev->events_mask & BIT(MEI_CL_EVENT_NOTIF)))
|
||||
return;
|
||||
return false;
|
||||
|
||||
if (!cl->notify_ev)
|
||||
return;
|
||||
return false;
|
||||
|
||||
set_bit(MEI_CL_EVENT_NOTIF, &cldev->events);
|
||||
|
||||
schedule_work(&cldev->event_work);
|
||||
|
||||
cl->notify_ev = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* mei_cl_bus_rx_event - schedule rx evenet
|
||||
* mei_cl_bus_rx_event - schedule rx event
|
||||
*
|
||||
* @cl: host client
|
||||
*
|
||||
* Return: true if event was scheduled
|
||||
* false if the client is not waiting for event
|
||||
*/
|
||||
void mei_cl_bus_rx_event(struct mei_cl *cl)
|
||||
bool mei_cl_bus_rx_event(struct mei_cl *cl)
|
||||
{
|
||||
struct mei_cl_device *cldev = cl->cldev;
|
||||
|
||||
if (!cldev || !cldev->event_cb)
|
||||
return;
|
||||
return false;
|
||||
|
||||
if (!(cldev->events_mask & BIT(MEI_CL_EVENT_RX)))
|
||||
return;
|
||||
return false;
|
||||
|
||||
set_bit(MEI_CL_EVENT_RX, &cldev->events);
|
||||
|
||||
schedule_work(&cldev->event_work);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -398,7 +415,7 @@ int mei_cldev_enable(struct mei_cl_device *cldev)
|
|||
|
||||
if (!cl) {
|
||||
mutex_lock(&bus->device_lock);
|
||||
cl = mei_cl_alloc_linked(bus, MEI_HOST_CLIENT_ID_ANY);
|
||||
cl = mei_cl_alloc_linked(bus);
|
||||
mutex_unlock(&bus->device_lock);
|
||||
if (IS_ERR(cl))
|
||||
return PTR_ERR(cl);
|
||||
|
@ -958,6 +975,22 @@ void mei_cl_bus_rescan(struct mei_device *bus)
|
|||
dev_dbg(bus->dev, "rescan end");
|
||||
}
|
||||
|
||||
void mei_cl_bus_rescan_work(struct work_struct *work)
|
||||
{
|
||||
struct mei_device *bus =
|
||||
container_of(work, struct mei_device, bus_rescan_work);
|
||||
struct mei_me_client *me_cl;
|
||||
|
||||
mutex_lock(&bus->device_lock);
|
||||
me_cl = mei_me_cl_by_uuid(bus, &mei_amthif_guid);
|
||||
if (me_cl)
|
||||
mei_amthif_host_init(bus, me_cl);
|
||||
mei_me_cl_put(me_cl);
|
||||
mutex_unlock(&bus->device_lock);
|
||||
|
||||
mei_cl_bus_rescan(bus);
|
||||
}
|
||||
|
||||
int __mei_cldev_driver_register(struct mei_cl_driver *cldrv,
|
||||
struct module *owner)
|
||||
{
|
||||
|
|
|
@ -359,7 +359,7 @@ void mei_io_cb_free(struct mei_cl_cb *cb)
|
|||
* Return: mei_cl_cb pointer or NULL;
|
||||
*/
|
||||
struct mei_cl_cb *mei_io_cb_init(struct mei_cl *cl, enum mei_cb_file_ops type,
|
||||
struct file *fp)
|
||||
const struct file *fp)
|
||||
{
|
||||
struct mei_cl_cb *cb;
|
||||
|
||||
|
@ -368,7 +368,7 @@ struct mei_cl_cb *mei_io_cb_init(struct mei_cl *cl, enum mei_cb_file_ops type,
|
|||
return NULL;
|
||||
|
||||
INIT_LIST_HEAD(&cb->list);
|
||||
cb->file_object = fp;
|
||||
cb->fp = fp;
|
||||
cb->cl = cl;
|
||||
cb->buf_idx = 0;
|
||||
cb->fop_type = type;
|
||||
|
@ -455,7 +455,8 @@ int mei_io_cb_alloc_buf(struct mei_cl_cb *cb, size_t length)
|
|||
* Return: cb on success and NULL on failure
|
||||
*/
|
||||
struct mei_cl_cb *mei_cl_alloc_cb(struct mei_cl *cl, size_t length,
|
||||
enum mei_cb_file_ops type, struct file *fp)
|
||||
enum mei_cb_file_ops type,
|
||||
const struct file *fp)
|
||||
{
|
||||
struct mei_cl_cb *cb;
|
||||
|
||||
|
@ -485,7 +486,7 @@ struct mei_cl_cb *mei_cl_read_cb(const struct mei_cl *cl, const struct file *fp)
|
|||
struct mei_cl_cb *cb;
|
||||
|
||||
list_for_each_entry(cb, &cl->rd_completed, list)
|
||||
if (!fp || fp == cb->file_object)
|
||||
if (!fp || fp == cb->fp)
|
||||
return cb;
|
||||
|
||||
return NULL;
|
||||
|
@ -503,12 +504,12 @@ 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->file_object)
|
||||
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->file_object)
|
||||
if (!fp || fp == cb->fp)
|
||||
mei_io_cb_free(cb);
|
||||
}
|
||||
|
||||
|
@ -535,7 +536,6 @@ int mei_cl_flush_queues(struct mei_cl *cl, const struct file *fp)
|
|||
mei_io_list_flush(&cl->dev->ctrl_wr_list, cl);
|
||||
mei_io_list_flush(&cl->dev->ctrl_rd_list, cl);
|
||||
mei_io_list_flush(&cl->dev->amthif_cmd_list, cl);
|
||||
mei_io_list_flush(&cl->dev->amthif_rd_complete_list, cl);
|
||||
|
||||
mei_cl_read_cb_flush(cl, fp);
|
||||
|
||||
|
@ -587,27 +587,23 @@ struct mei_cl *mei_cl_allocate(struct mei_device *dev)
|
|||
* mei_cl_link - allocate host id in the host map
|
||||
*
|
||||
* @cl: host client
|
||||
* @id: fixed host id or MEI_HOST_CLIENT_ID_ANY (-1) for generic one
|
||||
*
|
||||
* Return: 0 on success
|
||||
* -EINVAL on incorrect values
|
||||
* -EMFILE if open count exceeded.
|
||||
*/
|
||||
int mei_cl_link(struct mei_cl *cl, int id)
|
||||
int mei_cl_link(struct mei_cl *cl)
|
||||
{
|
||||
struct mei_device *dev;
|
||||
long open_handle_count;
|
||||
int id;
|
||||
|
||||
if (WARN_ON(!cl || !cl->dev))
|
||||
return -EINVAL;
|
||||
|
||||
dev = cl->dev;
|
||||
|
||||
/* If Id is not assigned get one*/
|
||||
if (id == MEI_HOST_CLIENT_ID_ANY)
|
||||
id = find_first_zero_bit(dev->host_clients_map,
|
||||
MEI_CLIENTS_MAX);
|
||||
|
||||
id = find_first_zero_bit(dev->host_clients_map, MEI_CLIENTS_MAX);
|
||||
if (id >= MEI_CLIENTS_MAX) {
|
||||
dev_err(dev->dev, "id exceeded %d", MEI_CLIENTS_MAX);
|
||||
return -EMFILE;
|
||||
|
@ -648,7 +644,7 @@ int mei_cl_unlink(struct mei_cl *cl)
|
|||
if (!cl)
|
||||
return 0;
|
||||
|
||||
/* wd and amthif might not be initialized */
|
||||
/* amthif might not be initialized */
|
||||
if (!cl->dev)
|
||||
return 0;
|
||||
|
||||
|
@ -670,31 +666,12 @@ int mei_cl_unlink(struct mei_cl *cl)
|
|||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void mei_host_client_init(struct work_struct *work)
|
||||
void mei_host_client_init(struct mei_device *dev)
|
||||
{
|
||||
struct mei_device *dev =
|
||||
container_of(work, struct mei_device, init_work);
|
||||
struct mei_me_client *me_cl;
|
||||
|
||||
mutex_lock(&dev->device_lock);
|
||||
|
||||
|
||||
me_cl = mei_me_cl_by_uuid(dev, &mei_amthif_guid);
|
||||
if (me_cl)
|
||||
mei_amthif_host_init(dev, me_cl);
|
||||
mei_me_cl_put(me_cl);
|
||||
|
||||
me_cl = mei_me_cl_by_uuid(dev, &mei_wd_guid);
|
||||
if (me_cl)
|
||||
mei_wd_host_init(dev, me_cl);
|
||||
mei_me_cl_put(me_cl);
|
||||
|
||||
dev->dev_state = MEI_DEV_ENABLED;
|
||||
dev->reset_count = 0;
|
||||
mutex_unlock(&dev->device_lock);
|
||||
|
||||
mei_cl_bus_rescan(dev);
|
||||
schedule_work(&dev->bus_rescan_work);
|
||||
|
||||
pm_runtime_mark_last_busy(dev->dev);
|
||||
dev_dbg(dev->dev, "rpm: autosuspend\n");
|
||||
|
@ -725,6 +702,33 @@ bool mei_hbuf_acquire(struct mei_device *dev)
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* mei_cl_wake_all - wake up readers, writers and event waiters so
|
||||
* they can be interrupted
|
||||
*
|
||||
* @cl: host client
|
||||
*/
|
||||
static void mei_cl_wake_all(struct mei_cl *cl)
|
||||
{
|
||||
struct mei_device *dev = cl->dev;
|
||||
|
||||
/* synchronized under device mutex */
|
||||
if (waitqueue_active(&cl->rx_wait)) {
|
||||
cl_dbg(dev, cl, "Waking up reading client!\n");
|
||||
wake_up_interruptible(&cl->rx_wait);
|
||||
}
|
||||
/* synchronized under device mutex */
|
||||
if (waitqueue_active(&cl->tx_wait)) {
|
||||
cl_dbg(dev, cl, "Waking up writing client!\n");
|
||||
wake_up_interruptible(&cl->tx_wait);
|
||||
}
|
||||
/* synchronized under device mutex */
|
||||
if (waitqueue_active(&cl->ev_wait)) {
|
||||
cl_dbg(dev, cl, "Waking up waiting for event clients!\n");
|
||||
wake_up_interruptible(&cl->ev_wait);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* mei_cl_set_disconnected - set disconnected state and clear
|
||||
* associated states and resources
|
||||
|
@ -740,8 +744,11 @@ void mei_cl_set_disconnected(struct mei_cl *cl)
|
|||
return;
|
||||
|
||||
cl->state = MEI_FILE_DISCONNECTED;
|
||||
mei_io_list_free(&dev->write_list, cl);
|
||||
mei_io_list_free(&dev->write_waiting_list, cl);
|
||||
mei_io_list_flush(&dev->ctrl_rd_list, cl);
|
||||
mei_io_list_flush(&dev->ctrl_wr_list, cl);
|
||||
mei_cl_wake_all(cl);
|
||||
cl->mei_flow_ctrl_creds = 0;
|
||||
cl->timer_count = 0;
|
||||
|
||||
|
@ -1034,7 +1041,7 @@ int mei_cl_irq_connect(struct mei_cl *cl, struct mei_cl_cb *cb,
|
|||
* Return: 0 on success, <0 on failure.
|
||||
*/
|
||||
int mei_cl_connect(struct mei_cl *cl, struct mei_me_client *me_cl,
|
||||
struct file *file)
|
||||
const struct file *file)
|
||||
{
|
||||
struct mei_device *dev;
|
||||
struct mei_cl_cb *cb;
|
||||
|
@ -1119,11 +1126,10 @@ nortpm:
|
|||
* mei_cl_alloc_linked - allocate and link host client
|
||||
*
|
||||
* @dev: the device structure
|
||||
* @id: fixed host id or MEI_HOST_CLIENT_ID_ANY (-1) for generic one
|
||||
*
|
||||
* Return: cl on success ERR_PTR on failure
|
||||
*/
|
||||
struct mei_cl *mei_cl_alloc_linked(struct mei_device *dev, int id)
|
||||
struct mei_cl *mei_cl_alloc_linked(struct mei_device *dev)
|
||||
{
|
||||
struct mei_cl *cl;
|
||||
int ret;
|
||||
|
@ -1134,7 +1140,7 @@ struct mei_cl *mei_cl_alloc_linked(struct mei_device *dev, int id)
|
|||
goto err;
|
||||
}
|
||||
|
||||
ret = mei_cl_link(cl, id);
|
||||
ret = mei_cl_link(cl);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
|
@ -1149,11 +1155,12 @@ err:
|
|||
/**
|
||||
* mei_cl_flow_ctrl_creds - checks flow_control credits for cl.
|
||||
*
|
||||
* @cl: private data of the file object
|
||||
* @cl: host client
|
||||
* @fp: the file pointer associated with the pointer
|
||||
*
|
||||
* Return: 1 if mei_flow_ctrl_creds >0, 0 - otherwise.
|
||||
*/
|
||||
int mei_cl_flow_ctrl_creds(struct mei_cl *cl)
|
||||
static int mei_cl_flow_ctrl_creds(struct mei_cl *cl, const struct file *fp)
|
||||
{
|
||||
int rets;
|
||||
|
||||
|
@ -1164,7 +1171,7 @@ int mei_cl_flow_ctrl_creds(struct mei_cl *cl)
|
|||
return 1;
|
||||
|
||||
if (mei_cl_is_fixed_address(cl)) {
|
||||
rets = mei_cl_read_start(cl, mei_cl_mtu(cl), NULL);
|
||||
rets = mei_cl_read_start(cl, mei_cl_mtu(cl), fp);
|
||||
if (rets && rets != -EBUSY)
|
||||
return rets;
|
||||
return 1;
|
||||
|
@ -1186,7 +1193,7 @@ int mei_cl_flow_ctrl_creds(struct mei_cl *cl)
|
|||
* 0 on success
|
||||
* -EINVAL when ctrl credits are <= 0
|
||||
*/
|
||||
int mei_cl_flow_ctrl_reduce(struct mei_cl *cl)
|
||||
static int mei_cl_flow_ctrl_reduce(struct mei_cl *cl)
|
||||
{
|
||||
if (WARN_ON(!cl || !cl->me_cl))
|
||||
return -EINVAL;
|
||||
|
@ -1283,7 +1290,8 @@ int mei_cl_irq_notify(struct mei_cl *cl, struct mei_cl_cb *cb,
|
|||
*
|
||||
* Return: 0 on such and error otherwise.
|
||||
*/
|
||||
int mei_cl_notify_request(struct mei_cl *cl, struct file *file, u8 request)
|
||||
int mei_cl_notify_request(struct mei_cl *cl,
|
||||
const struct file *file, u8 request)
|
||||
{
|
||||
struct mei_device *dev;
|
||||
struct mei_cl_cb *cb;
|
||||
|
@ -1368,12 +1376,12 @@ void mei_cl_notify(struct mei_cl *cl)
|
|||
|
||||
cl_dbg(dev, cl, "notify event");
|
||||
cl->notify_ev = true;
|
||||
wake_up_interruptible_all(&cl->ev_wait);
|
||||
if (!mei_cl_bus_notify_event(cl))
|
||||
wake_up_interruptible(&cl->ev_wait);
|
||||
|
||||
if (cl->ev_async)
|
||||
kill_fasync(&cl->ev_async, SIGIO, POLL_PRI);
|
||||
|
||||
mei_cl_bus_notify_event(cl);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1421,6 +1429,25 @@ out:
|
|||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* mei_cl_is_read_fc_cb - check if read cb is waiting for flow control
|
||||
* for given host client
|
||||
*
|
||||
* @cl: host client
|
||||
*
|
||||
* Return: true, if found at least one cb.
|
||||
*/
|
||||
static bool mei_cl_is_read_fc_cb(struct mei_cl *cl)
|
||||
{
|
||||
struct mei_device *dev = cl->dev;
|
||||
struct mei_cl_cb *cb;
|
||||
|
||||
list_for_each_entry(cb, &dev->ctrl_wr_list.list, list)
|
||||
if (cb->fop_type == MEI_FOP_READ && cb->cl == cl)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* mei_cl_read_start - the start read client message function.
|
||||
*
|
||||
|
@ -1430,7 +1457,7 @@ out:
|
|||
*
|
||||
* Return: 0 on success, <0 on failure.
|
||||
*/
|
||||
int mei_cl_read_start(struct mei_cl *cl, size_t length, struct file *fp)
|
||||
int mei_cl_read_start(struct mei_cl *cl, size_t length, const struct file *fp)
|
||||
{
|
||||
struct mei_device *dev;
|
||||
struct mei_cl_cb *cb;
|
||||
|
@ -1445,7 +1472,7 @@ int mei_cl_read_start(struct mei_cl *cl, size_t length, struct file *fp)
|
|||
return -ENODEV;
|
||||
|
||||
/* HW currently supports only one pending read */
|
||||
if (!list_empty(&cl->rd_pending))
|
||||
if (!list_empty(&cl->rd_pending) || mei_cl_is_read_fc_cb(cl))
|
||||
return -EBUSY;
|
||||
|
||||
if (!mei_me_cl_is_active(cl->me_cl)) {
|
||||
|
@ -1524,7 +1551,7 @@ int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb,
|
|||
|
||||
first_chunk = cb->buf_idx == 0;
|
||||
|
||||
rets = first_chunk ? mei_cl_flow_ctrl_creds(cl) : 1;
|
||||
rets = first_chunk ? mei_cl_flow_ctrl_creds(cl, cb->fp) : 1;
|
||||
if (rets < 0)
|
||||
return rets;
|
||||
|
||||
|
@ -1556,7 +1583,7 @@ int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb,
|
|||
return 0;
|
||||
}
|
||||
|
||||
cl_dbg(dev, cl, "buf: size = %d idx = %lu\n",
|
||||
cl_dbg(dev, cl, "buf: size = %zu idx = %zu\n",
|
||||
cb->buf.size, cb->buf_idx);
|
||||
|
||||
rets = mei_write_message(dev, &mei_hdr, buf->data + cb->buf_idx);
|
||||
|
@ -1618,7 +1645,7 @@ int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb, bool blocking)
|
|||
if (rets < 0 && rets != -EINPROGRESS) {
|
||||
pm_runtime_put_noidle(dev->dev);
|
||||
cl_err(dev, cl, "rpm: get failed %d\n", rets);
|
||||
return rets;
|
||||
goto free;
|
||||
}
|
||||
|
||||
cb->buf_idx = 0;
|
||||
|
@ -1630,7 +1657,7 @@ int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb, bool blocking)
|
|||
mei_hdr.msg_complete = 0;
|
||||
mei_hdr.internal = cb->internal;
|
||||
|
||||
rets = mei_cl_flow_ctrl_creds(cl);
|
||||
rets = mei_cl_flow_ctrl_creds(cl, cb->fp);
|
||||
if (rets < 0)
|
||||
goto err;
|
||||
|
||||
|
@ -1677,7 +1704,8 @@ out:
|
|||
|
||||
mutex_unlock(&dev->device_lock);
|
||||
rets = wait_event_interruptible(cl->tx_wait,
|
||||
cl->writing_state == MEI_WRITE_COMPLETE);
|
||||
cl->writing_state == MEI_WRITE_COMPLETE ||
|
||||
(!mei_cl_is_connected(cl)));
|
||||
mutex_lock(&dev->device_lock);
|
||||
/* wait_event_interruptible returns -ERESTARTSYS */
|
||||
if (rets) {
|
||||
|
@ -1685,6 +1713,10 @@ out:
|
|||
rets = -EINTR;
|
||||
goto err;
|
||||
}
|
||||
if (cl->writing_state != MEI_WRITE_COMPLETE) {
|
||||
rets = -EFAULT;
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
rets = size;
|
||||
|
@ -1692,6 +1724,8 @@ err:
|
|||
cl_dbg(dev, cl, "rpm: autosuspend\n");
|
||||
pm_runtime_mark_last_busy(dev->dev);
|
||||
pm_runtime_put_autosuspend(dev->dev);
|
||||
free:
|
||||
mei_io_cb_free(cb);
|
||||
|
||||
return rets;
|
||||
}
|
||||
|
@ -1721,10 +1755,8 @@ void mei_cl_complete(struct mei_cl *cl, struct mei_cl_cb *cb)
|
|||
|
||||
case MEI_FOP_READ:
|
||||
list_add_tail(&cb->list, &cl->rd_completed);
|
||||
if (waitqueue_active(&cl->rx_wait))
|
||||
wake_up_interruptible_all(&cl->rx_wait);
|
||||
else
|
||||
mei_cl_bus_rx_event(cl);
|
||||
if (!mei_cl_bus_rx_event(cl))
|
||||
wake_up_interruptible(&cl->rx_wait);
|
||||
break;
|
||||
|
||||
case MEI_FOP_CONNECT:
|
||||
|
@ -1753,44 +1785,3 @@ void mei_cl_all_disconnect(struct mei_device *dev)
|
|||
list_for_each_entry(cl, &dev->file_list, link)
|
||||
mei_cl_set_disconnected(cl);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* mei_cl_all_wakeup - wake up all readers and writers they can be interrupted
|
||||
*
|
||||
* @dev: mei device
|
||||
*/
|
||||
void mei_cl_all_wakeup(struct mei_device *dev)
|
||||
{
|
||||
struct mei_cl *cl;
|
||||
|
||||
list_for_each_entry(cl, &dev->file_list, link) {
|
||||
if (waitqueue_active(&cl->rx_wait)) {
|
||||
cl_dbg(dev, cl, "Waking up reading client!\n");
|
||||
wake_up_interruptible(&cl->rx_wait);
|
||||
}
|
||||
if (waitqueue_active(&cl->tx_wait)) {
|
||||
cl_dbg(dev, cl, "Waking up writing client!\n");
|
||||
wake_up_interruptible(&cl->tx_wait);
|
||||
}
|
||||
|
||||
/* synchronized under device mutex */
|
||||
if (waitqueue_active(&cl->ev_wait)) {
|
||||
cl_dbg(dev, cl, "Waking up waiting for event clients!\n");
|
||||
wake_up_interruptible(&cl->ev_wait);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* mei_cl_all_write_clear - clear all pending writes
|
||||
*
|
||||
* @dev: mei device
|
||||
*/
|
||||
void mei_cl_all_write_clear(struct mei_device *dev)
|
||||
{
|
||||
mei_io_list_free(&dev->write_list, NULL);
|
||||
mei_io_list_free(&dev->write_waiting_list, NULL);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
#define _MEI_CLIENT_H_
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/watchdog.h>
|
||||
#include <linux/poll.h>
|
||||
#include <linux/mei.h>
|
||||
|
||||
|
@ -84,7 +83,7 @@ static inline u8 mei_me_cl_ver(const struct mei_me_client *me_cl)
|
|||
* MEI IO Functions
|
||||
*/
|
||||
struct mei_cl_cb *mei_io_cb_init(struct mei_cl *cl, enum mei_cb_file_ops type,
|
||||
struct file *fp);
|
||||
const struct file *fp);
|
||||
void mei_io_cb_free(struct mei_cl_cb *priv_cb);
|
||||
int mei_io_cb_alloc_buf(struct mei_cl_cb *cb, size_t length);
|
||||
|
||||
|
@ -108,21 +107,19 @@ struct mei_cl *mei_cl_allocate(struct mei_device *dev);
|
|||
void mei_cl_init(struct mei_cl *cl, struct mei_device *dev);
|
||||
|
||||
|
||||
int mei_cl_link(struct mei_cl *cl, int id);
|
||||
int mei_cl_link(struct mei_cl *cl);
|
||||
int mei_cl_unlink(struct mei_cl *cl);
|
||||
|
||||
struct mei_cl *mei_cl_alloc_linked(struct mei_device *dev, int id);
|
||||
struct mei_cl *mei_cl_alloc_linked(struct mei_device *dev);
|
||||
|
||||
struct mei_cl_cb *mei_cl_read_cb(const struct mei_cl *cl,
|
||||
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,
|
||||
enum mei_cb_file_ops type, struct file *fp);
|
||||
enum mei_cb_file_ops type,
|
||||
const struct file *fp);
|
||||
int mei_cl_flush_queues(struct mei_cl *cl, const struct file *fp);
|
||||
|
||||
int mei_cl_flow_ctrl_creds(struct mei_cl *cl);
|
||||
|
||||
int mei_cl_flow_ctrl_reduce(struct mei_cl *cl);
|
||||
/*
|
||||
* MEI input output function prototype
|
||||
*/
|
||||
|
@ -217,10 +214,10 @@ void mei_cl_set_disconnected(struct mei_cl *cl);
|
|||
int mei_cl_irq_disconnect(struct mei_cl *cl, struct mei_cl_cb *cb,
|
||||
struct mei_cl_cb *cmpl_list);
|
||||
int mei_cl_connect(struct mei_cl *cl, struct mei_me_client *me_cl,
|
||||
struct file *file);
|
||||
const struct file *file);
|
||||
int mei_cl_irq_connect(struct mei_cl *cl, struct mei_cl_cb *cb,
|
||||
struct mei_cl_cb *cmpl_list);
|
||||
int mei_cl_read_start(struct mei_cl *cl, size_t length, struct file *fp);
|
||||
int mei_cl_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,
|
||||
struct mei_cl_cb *cmpl_list);
|
||||
int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb, bool blocking);
|
||||
|
@ -229,19 +226,18 @@ int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb,
|
|||
|
||||
void mei_cl_complete(struct mei_cl *cl, struct mei_cl_cb *cb);
|
||||
|
||||
void mei_host_client_init(struct work_struct *work);
|
||||
void mei_host_client_init(struct mei_device *dev);
|
||||
|
||||
u8 mei_cl_notify_fop2req(enum mei_cb_file_ops fop);
|
||||
enum mei_cb_file_ops mei_cl_notify_req2fop(u8 request);
|
||||
int mei_cl_notify_request(struct mei_cl *cl, struct file *file, u8 request);
|
||||
int mei_cl_notify_request(struct mei_cl *cl,
|
||||
const struct file *file, u8 request);
|
||||
int mei_cl_irq_notify(struct mei_cl *cl, struct mei_cl_cb *cb,
|
||||
struct mei_cl_cb *cmpl_list);
|
||||
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_all_disconnect(struct mei_device *dev);
|
||||
void mei_cl_all_wakeup(struct mei_device *dev);
|
||||
void mei_cl_all_write_clear(struct mei_device *dev);
|
||||
|
||||
#define MEI_CL_FMT "cl:host=%02d me=%02d "
|
||||
#define MEI_CL_PRM(cl) (cl)->host_client_id, mei_cl_me_id(cl)
|
||||
|
@ -249,6 +245,9 @@ void mei_cl_all_write_clear(struct mei_device *dev);
|
|||
#define cl_dbg(dev, cl, format, arg...) \
|
||||
dev_dbg((dev)->dev, MEI_CL_FMT format, MEI_CL_PRM(cl), ##arg)
|
||||
|
||||
#define cl_warn(dev, cl, format, arg...) \
|
||||
dev_warn((dev)->dev, MEI_CL_FMT format, MEI_CL_PRM(cl), ##arg)
|
||||
|
||||
#define cl_err(dev, cl, format, arg...) \
|
||||
dev_err((dev)->dev, MEI_CL_FMT format, MEI_CL_PRM(cl), ##arg)
|
||||
|
||||
|
|
|
@ -50,6 +50,7 @@ static ssize_t mei_dbgfs_read_meclients(struct file *fp, char __user *ubuf,
|
|||
}
|
||||
|
||||
pos += scnprintf(buf + pos, bufsz - pos, HDR);
|
||||
#undef HDR
|
||||
|
||||
/* if the driver is not enabled the list won't be consistent */
|
||||
if (dev->dev_state != MEI_DEV_ENABLED)
|
||||
|
@ -90,24 +91,38 @@ static ssize_t mei_dbgfs_read_active(struct file *fp, char __user *ubuf,
|
|||
{
|
||||
struct mei_device *dev = fp->private_data;
|
||||
struct mei_cl *cl;
|
||||
const size_t bufsz = 1024;
|
||||
size_t bufsz = 1;
|
||||
char *buf;
|
||||
int i = 0;
|
||||
int pos = 0;
|
||||
int ret;
|
||||
|
||||
#define HDR " |me|host|state|rd|wr|\n"
|
||||
|
||||
if (!dev)
|
||||
return -ENODEV;
|
||||
|
||||
buf = kzalloc(bufsz, GFP_KERNEL);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
pos += scnprintf(buf + pos, bufsz - pos,
|
||||
" |me|host|state|rd|wr|\n");
|
||||
|
||||
mutex_lock(&dev->device_lock);
|
||||
|
||||
/*
|
||||
* if the driver is not enabled the list won't be consistent,
|
||||
* we output empty table
|
||||
*/
|
||||
if (dev->dev_state == MEI_DEV_ENABLED)
|
||||
list_for_each_entry(cl, &dev->file_list, link)
|
||||
bufsz++;
|
||||
|
||||
bufsz *= sizeof(HDR) + 1;
|
||||
|
||||
buf = kzalloc(bufsz, GFP_KERNEL);
|
||||
if (!buf) {
|
||||
mutex_unlock(&dev->device_lock);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
pos += scnprintf(buf + pos, bufsz - pos, HDR);
|
||||
#undef HDR
|
||||
|
||||
/* if the driver is not enabled the list won't be consistent */
|
||||
if (dev->dev_state != MEI_DEV_ENABLED)
|
||||
goto out;
|
||||
|
@ -115,7 +130,7 @@ static ssize_t mei_dbgfs_read_active(struct file *fp, char __user *ubuf,
|
|||
list_for_each_entry(cl, &dev->file_list, link) {
|
||||
|
||||
pos += scnprintf(buf + pos, bufsz - pos,
|
||||
"%2d|%2d|%4d|%5d|%2d|%2d|\n",
|
||||
"%3d|%2d|%4d|%5d|%2d|%2d|\n",
|
||||
i, mei_cl_me_id(cl), cl->host_client_id, cl->state,
|
||||
!list_empty(&cl->rd_completed), cl->writing_state);
|
||||
i++;
|
||||
|
@ -150,16 +165,21 @@ static ssize_t mei_dbgfs_read_devstate(struct file *fp, char __user *ubuf,
|
|||
pos += scnprintf(buf + pos, bufsz - pos, "hbm: %s\n",
|
||||
mei_hbm_state_str(dev->hbm_state));
|
||||
|
||||
if (dev->hbm_state == MEI_HBM_STARTED) {
|
||||
if (dev->hbm_state >= MEI_HBM_ENUM_CLIENTS &&
|
||||
dev->hbm_state <= MEI_HBM_STARTED) {
|
||||
pos += scnprintf(buf + pos, bufsz - pos, "hbm features:\n");
|
||||
pos += scnprintf(buf + pos, bufsz - pos, "\tPG: %01d\n",
|
||||
dev->hbm_f_pg_supported);
|
||||
pos += scnprintf(buf + pos, bufsz - pos, "\tDC: %01d\n",
|
||||
dev->hbm_f_dc_supported);
|
||||
pos += scnprintf(buf + pos, bufsz - pos, "\tIE: %01d\n",
|
||||
dev->hbm_f_ie_supported);
|
||||
pos += scnprintf(buf + pos, bufsz - pos, "\tDOT: %01d\n",
|
||||
dev->hbm_f_dot_supported);
|
||||
pos += scnprintf(buf + pos, bufsz - pos, "\tEV: %01d\n",
|
||||
dev->hbm_f_ev_supported);
|
||||
pos += scnprintf(buf + pos, bufsz - pos, "\tFA: %01d\n",
|
||||
dev->hbm_f_fa_supported);
|
||||
}
|
||||
|
||||
pos += scnprintf(buf + pos, bufsz - pos, "pg: %s, %s\n",
|
||||
|
@ -175,6 +195,30 @@ static const struct file_operations mei_dbgfs_fops_devstate = {
|
|||
.llseek = generic_file_llseek,
|
||||
};
|
||||
|
||||
static ssize_t mei_dbgfs_write_allow_fa(struct file *file,
|
||||
const char __user *user_buf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
struct mei_device *dev;
|
||||
int ret;
|
||||
|
||||
dev = container_of(file->private_data,
|
||||
struct mei_device, allow_fixed_address);
|
||||
|
||||
ret = debugfs_write_file_bool(file, user_buf, count, ppos);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
dev->override_fixed_address = true;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct file_operations mei_dbgfs_fops_allow_fa = {
|
||||
.open = simple_open,
|
||||
.read = debugfs_read_file_bool,
|
||||
.write = mei_dbgfs_write_allow_fa,
|
||||
.llseek = generic_file_llseek,
|
||||
};
|
||||
|
||||
/**
|
||||
* mei_dbgfs_deregister - Remove the debugfs files and directories
|
||||
*
|
||||
|
@ -224,8 +268,9 @@ int mei_dbgfs_register(struct mei_device *dev, const char *name)
|
|||
dev_err(dev->dev, "devstate: registration failed\n");
|
||||
goto err;
|
||||
}
|
||||
f = debugfs_create_bool("allow_fixed_address", S_IRUSR | S_IWUSR, dir,
|
||||
&dev->allow_fixed_address);
|
||||
f = debugfs_create_file("allow_fixed_address", S_IRUSR | S_IWUSR, dir,
|
||||
&dev->allow_fixed_address,
|
||||
&mei_dbgfs_fops_allow_fa);
|
||||
if (!f) {
|
||||
dev_err(dev->dev, "allow_fixed_address: registration failed\n");
|
||||
goto err;
|
||||
|
|
|
@ -301,7 +301,10 @@ static int mei_hbm_enum_clients_req(struct mei_device *dev)
|
|||
enum_req = (struct hbm_host_enum_request *)dev->wr_msg.data;
|
||||
memset(enum_req, 0, len);
|
||||
enum_req->hbm_cmd = HOST_ENUM_REQ_CMD;
|
||||
enum_req->allow_add = dev->hbm_f_dc_supported;
|
||||
enum_req->flags |= dev->hbm_f_dc_supported ?
|
||||
MEI_HBM_ENUM_F_ALLOW_ADD : 0;
|
||||
enum_req->flags |= dev->hbm_f_ie_supported ?
|
||||
MEI_HBM_ENUM_F_IMMEDIATE_ENUM : 0;
|
||||
|
||||
ret = mei_write_message(dev, mei_hdr, dev->wr_msg.data);
|
||||
if (ret) {
|
||||
|
@ -401,6 +404,9 @@ static int mei_hbm_fw_add_cl_req(struct mei_device *dev,
|
|||
if (ret)
|
||||
status = !MEI_HBMS_SUCCESS;
|
||||
|
||||
if (dev->dev_state == MEI_DEV_ENABLED)
|
||||
schedule_work(&dev->bus_rescan_work);
|
||||
|
||||
return mei_hbm_add_cl_resp(dev, req->me_addr, status);
|
||||
}
|
||||
|
||||
|
@ -543,7 +549,7 @@ static int mei_hbm_prop_req(struct mei_device *dev)
|
|||
/* We got all client properties */
|
||||
if (next_client_index == MEI_CLIENTS_MAX) {
|
||||
dev->hbm_state = MEI_HBM_STARTED;
|
||||
schedule_work(&dev->init_work);
|
||||
mei_host_client_init(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -789,8 +795,11 @@ static void mei_hbm_cl_connect_res(struct mei_device *dev, struct mei_cl *cl,
|
|||
cl->state = MEI_FILE_CONNECTED;
|
||||
else {
|
||||
cl->state = MEI_FILE_DISCONNECT_REPLY;
|
||||
if (rs->status == MEI_CL_CONN_NOT_FOUND)
|
||||
if (rs->status == MEI_CL_CONN_NOT_FOUND) {
|
||||
mei_me_cl_del(dev, cl->me_cl);
|
||||
if (dev->dev_state == MEI_DEV_ENABLED)
|
||||
schedule_work(&dev->bus_rescan_work);
|
||||
}
|
||||
}
|
||||
cl->status = mei_cl_conn_status_to_errno(rs->status);
|
||||
}
|
||||
|
@ -866,7 +875,7 @@ static int mei_hbm_fw_disconnect_req(struct mei_device *dev,
|
|||
|
||||
cl = mei_hbm_cl_find_by_cmd(dev, disconnect_req);
|
||||
if (cl) {
|
||||
cl_dbg(dev, cl, "fw disconnect request received\n");
|
||||
cl_warn(dev, cl, "fw disconnect request received\n");
|
||||
cl->state = MEI_FILE_DISCONNECTING;
|
||||
cl->timer_count = 0;
|
||||
|
||||
|
@ -972,6 +981,9 @@ static void mei_hbm_config_features(struct mei_device *dev)
|
|||
if (dev->version.major_version >= HBM_MAJOR_VERSION_DC)
|
||||
dev->hbm_f_dc_supported = 1;
|
||||
|
||||
if (dev->version.major_version >= HBM_MAJOR_VERSION_IE)
|
||||
dev->hbm_f_ie_supported = 1;
|
||||
|
||||
/* disconnect on connect timeout instead of link reset */
|
||||
if (dev->version.major_version >= HBM_MAJOR_VERSION_DOT)
|
||||
dev->hbm_f_dot_supported = 1;
|
||||
|
@ -979,6 +991,10 @@ static void mei_hbm_config_features(struct mei_device *dev)
|
|||
/* Notification Event Support */
|
||||
if (dev->version.major_version >= HBM_MAJOR_VERSION_EV)
|
||||
dev->hbm_f_ev_supported = 1;
|
||||
|
||||
/* Fixed Address Client Support */
|
||||
if (dev->version.major_version >= HBM_MAJOR_VERSION_FA)
|
||||
dev->hbm_f_fa_supported = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue