From 4f0be26a7939d8cae5d010b9de33550a366ceef4 Mon Sep 17 00:00:00 2001 From: Jaewon Kim Date: Thu, 29 Jan 2015 17:45:23 +0900 Subject: extcon: max77693: Fix cable name of MHL-TA This patch fixes extcon cable name of MHL-TA instead of MHL_TA to unify cable name style. Signed-off-by: Jaewon Kim Signed-off-by: Chanwoo Choi diff --git a/drivers/extcon/extcon-max77693.c b/drivers/extcon/extcon-max77693.c index af165fd..9aca3b7 100644 --- a/drivers/extcon/extcon-max77693.c +++ b/drivers/extcon/extcon-max77693.c @@ -228,7 +228,7 @@ static const char *max77693_extcon_cable[] = { [EXTCON_CABLE_SLOW_CHARGER] = "Slow-charger", [EXTCON_CABLE_CHARGE_DOWNSTREAM] = "Charge-downstream", [EXTCON_CABLE_MHL] = "MHL", - [EXTCON_CABLE_MHL_TA] = "MHL_TA", + [EXTCON_CABLE_MHL_TA] = "MHL-TA", [EXTCON_CABLE_JIG_USB_ON] = "JIG-USB-ON", [EXTCON_CABLE_JIG_USB_OFF] = "JIG-USB-OFF", [EXTCON_CABLE_JIG_UART_OFF] = "JIG-UART-OFF", @@ -823,19 +823,19 @@ static int max77693_muic_chg_handler(struct max77693_muic_info *info) case MAX77693_MUIC_GND_MHL: case MAX77693_MUIC_GND_MHL_VB: /* - * MHL cable with MHL_TA(USB/TA) cable + * MHL cable with MHL-TA(USB/TA) cable * - MHL cable include two port(HDMI line and separate * micro-usb port. When the target connect MHL cable, - * extcon driver check whether MHL_TA(USB/TA) cable is - * connected. If MHL_TA cable is connected, extcon + * extcon driver check whether MHL-TA(USB/TA) cable is + * connected. If MHL-TA cable is connected, extcon * driver notify state to notifiee for charging battery. * - * Features of 'MHL_TA(USB/TA) with MHL cable' + * Features of 'MHL-TA(USB/TA) with MHL cable' * - Support MHL * - Support charging through micro-usb port without * data connection */ - extcon_set_cable_state(info->edev, "MHL_TA", attached); + extcon_set_cable_state(info->edev, "MHL-TA", attached); if (!cable_attached) extcon_set_cable_state(info->edev, "MHL", cable_attached); -- cgit v0.10.2 From 4c883abee08b13d7fac5e672159575bb7a3365a6 Mon Sep 17 00:00:00 2001 From: Jaewon Kim Date: Thu, 29 Jan 2015 17:45:24 +0900 Subject: extcon: max77693: Use HOST term to express USB-HOST cable instead of OTG term This patch unifies the term called 'USB_OTG' and 'USB_HOST' into USB_HOST. OTG(On-The-Go) function supports USB host and this driver sents 'USB-Host event. So, unifies term to USB_HOST. Signed-off-by: Jaewon Kim [cw00.choi: Fix patch title to indicate the correct meaning of patch] Signed-off-by: Chanwoo Choi diff --git a/drivers/extcon/extcon-max77693.c b/drivers/extcon/extcon-max77693.c index 9aca3b7..dfcc5cb 100644 --- a/drivers/extcon/extcon-max77693.c +++ b/drivers/extcon/extcon-max77693.c @@ -190,8 +190,8 @@ enum max77693_muic_acc_type { /* The below accessories have same ADC value so ADCLow and ADC1K bit is used to separate specific accessory */ /* ADC|VBVolot|ADCLow|ADC1K| */ - MAX77693_MUIC_GND_USB_OTG = 0x100, /* 0x0| 0| 0| 0| */ - MAX77693_MUIC_GND_USB_OTG_VB = 0x104, /* 0x0| 1| 0| 0| */ + MAX77693_MUIC_GND_USB_HOST = 0x100, /* 0x0| 0| 0| 0| */ + MAX77693_MUIC_GND_USB_HOST_VB = 0x104, /* 0x0| 1| 0| 0| */ MAX77693_MUIC_GND_AV_CABLE_LOAD = 0x102,/* 0x0| 0| 1| 0| */ MAX77693_MUIC_GND_MHL = 0x103, /* 0x0| 0| 1| 1| */ MAX77693_MUIC_GND_MHL_VB = 0x107, /* 0x0| 1| 1| 1| */ @@ -403,8 +403,8 @@ static int max77693_muic_get_cable_type(struct max77693_muic_info *info, /** * [0x1|VBVolt|ADCLow|ADC1K] - * [0x1| 0| 0| 0] USB_OTG - * [0x1| 1| 0| 0] USB_OTG_VB + * [0x1| 0| 0| 0] USB_HOST + * [0x1| 1| 0| 0] USB_HSOT_VB * [0x1| 0| 1| 0] Audio Video cable with load * [0x1| 0| 1| 1] MHL without charging cable * [0x1| 1| 1| 1] MHL with charging cable @@ -523,7 +523,7 @@ static int max77693_muic_dock_handler(struct max77693_muic_info *info, * - Support charging and data connection through micro-usb port * if USB cable is connected between target and host * device. - * - Support OTG device (Mouse/Keyboard) + * - Support OTG(On-The-Go) device (Ex: Mouse/Keyboard) */ ret = max77693_muic_set_path(info, info->path_usb, attached); if (ret < 0) @@ -609,9 +609,9 @@ static int max77693_muic_adc_ground_handler(struct max77693_muic_info *info) MAX77693_CABLE_GROUP_ADC_GND, &attached); switch (cable_type_gnd) { - case MAX77693_MUIC_GND_USB_OTG: - case MAX77693_MUIC_GND_USB_OTG_VB: - /* USB_OTG, PATH: AP_USB */ + case MAX77693_MUIC_GND_USB_HOST: + case MAX77693_MUIC_GND_USB_HOST_VB: + /* USB_HOST, PATH: AP_USB */ ret = max77693_muic_set_path(info, CONTROL1_SW_USB, attached); if (ret < 0) return ret; @@ -704,7 +704,7 @@ static int max77693_muic_adc_handler(struct max77693_muic_info *info) switch (cable_type) { case MAX77693_MUIC_ADC_GROUND: - /* USB_OTG/MHL/Audio */ + /* USB_HOST/MHL/Audio */ max77693_muic_adc_ground_handler(info); break; case MAX77693_MUIC_ADC_FACTORY_MODE_USB_OFF: @@ -886,7 +886,7 @@ static int max77693_muic_chg_handler(struct max77693_muic_info *info) * - Support charging and data connection through micro- * usb port if USB cable is connected between target * and host device - * - Support OTG device (Mouse/Keyboard) + * - Support OTG(On-The-Go) device (Ex: Mouse/Keyboard) */ ret = max77693_muic_set_path(info, info->path_usb, attached); -- cgit v0.10.2 From e52817faae359ce95c93c2b6eb88b16d4b430181 Mon Sep 17 00:00:00 2001 From: Roger Quadros Date: Mon, 2 Feb 2015 12:21:59 +0200 Subject: extcon: usb-gpio: Introduce gpio usb extcon driver This driver observes the USB ID pin connected over a GPIO and updates the USB cable extcon states accordingly. The existing GPIO extcon driver is not suitable for this purpose as it needs to be taught to understand USB cable states and it can't handle more than one cable per instance. For the USB case we need to handle 2 cable states. 1) USB (attach/detach) 2) USB-HOST (attach/detach) This driver can be easily updated in the future to handle VBUS events in case it happens to be available on GPIO for any platform. Signed-off-by: Roger Quadros Reviewed-by: Felipe Balbi Acked-by: Felipe Balbi Signed-off-by: Chanwoo Choi diff --git a/Documentation/devicetree/bindings/extcon/extcon-usb-gpio.txt b/Documentation/devicetree/bindings/extcon/extcon-usb-gpio.txt new file mode 100644 index 0000000..af0b903 --- /dev/null +++ b/Documentation/devicetree/bindings/extcon/extcon-usb-gpio.txt @@ -0,0 +1,18 @@ +USB GPIO Extcon device + +This is a virtual device used to generate USB cable states from the USB ID pin +connected to a GPIO pin. + +Required properties: +- compatible: Should be "linux,extcon-usb-gpio" +- id-gpio: gpio for USB ID pin. See gpio binding. + +Example: Examples of extcon-usb-gpio node in dra7-evm.dts as listed below: + extcon_usb1 { + compatible = "linux,extcon-usb-gpio"; + id-gpio = <&gpio6 1 GPIO_ACTIVE_HIGH>; + } + + &omap_dwc3_1 { + extcon = <&extcon_usb1>; + }; diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig index 6a1f7de..e4c01ab 100644 --- a/drivers/extcon/Kconfig +++ b/drivers/extcon/Kconfig @@ -93,4 +93,11 @@ config EXTCON_SM5502 Silicon Mitus SM5502. The SM5502 is a USB port accessory detector and switch. +config EXTCON_USB_GPIO + tristate "USB GPIO extcon support" + depends on GPIOLIB + help + Say Y here to enable GPIO based USB cable detection extcon support. + Used typically if GPIO is used for USB ID pin detection. + endif # MULTISTATE_SWITCH diff --git a/drivers/extcon/Makefile b/drivers/extcon/Makefile index 0370b42..6a08a98 100644 --- a/drivers/extcon/Makefile +++ b/drivers/extcon/Makefile @@ -12,3 +12,4 @@ obj-$(CONFIG_EXTCON_MAX8997) += extcon-max8997.o obj-$(CONFIG_EXTCON_PALMAS) += extcon-palmas.o obj-$(CONFIG_EXTCON_RT8973A) += extcon-rt8973a.o obj-$(CONFIG_EXTCON_SM5502) += extcon-sm5502.o +obj-$(CONFIG_EXTCON_USB_GPIO) += extcon-usb-gpio.o diff --git a/drivers/extcon/extcon-usb-gpio.c b/drivers/extcon/extcon-usb-gpio.c new file mode 100644 index 0000000..3f0bad3 --- /dev/null +++ b/drivers/extcon/extcon-usb-gpio.c @@ -0,0 +1,237 @@ +/** + * drivers/extcon/extcon-usb-gpio.c - USB GPIO extcon driver + * + * Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com + * Author: Roger Quadros + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define USB_GPIO_DEBOUNCE_MS 20 /* ms */ + +struct usb_extcon_info { + struct device *dev; + struct extcon_dev *edev; + + struct gpio_desc *id_gpiod; + int id_irq; + + unsigned long debounce_jiffies; + struct delayed_work wq_detcable; +}; + +/* List of detectable cables */ +enum { + EXTCON_CABLE_USB = 0, + EXTCON_CABLE_USB_HOST, + + EXTCON_CABLE_END, +}; + +static const char *usb_extcon_cable[] = { + [EXTCON_CABLE_USB] = "USB", + [EXTCON_CABLE_USB_HOST] = "USB-HOST", + NULL, +}; + +static void usb_extcon_detect_cable(struct work_struct *work) +{ + int id; + struct usb_extcon_info *info = container_of(to_delayed_work(work), + struct usb_extcon_info, + wq_detcable); + + /* check ID and update cable state */ + id = gpiod_get_value_cansleep(info->id_gpiod); + if (id) { + /* + * ID = 1 means USB HOST cable detached. + * As we don't have event for USB peripheral cable attached, + * we simulate USB peripheral attach here. + */ + extcon_set_cable_state(info->edev, + usb_extcon_cable[EXTCON_CABLE_USB_HOST], + false); + extcon_set_cable_state(info->edev, + usb_extcon_cable[EXTCON_CABLE_USB], + true); + } else { + /* + * ID = 0 means USB HOST cable attached. + * As we don't have event for USB peripheral cable detached, + * we simulate USB peripheral detach here. + */ + extcon_set_cable_state(info->edev, + usb_extcon_cable[EXTCON_CABLE_USB], + false); + extcon_set_cable_state(info->edev, + usb_extcon_cable[EXTCON_CABLE_USB_HOST], + true); + } +} + +static irqreturn_t usb_irq_handler(int irq, void *dev_id) +{ + struct usb_extcon_info *info = dev_id; + + queue_delayed_work(system_power_efficient_wq, &info->wq_detcable, + info->debounce_jiffies); + + return IRQ_HANDLED; +} + +static int usb_extcon_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct usb_extcon_info *info; + int ret; + + if (!np) + return -EINVAL; + + info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->dev = dev; + info->id_gpiod = devm_gpiod_get(&pdev->dev, "id"); + if (IS_ERR(info->id_gpiod)) { + dev_err(dev, "failed to get ID GPIO\n"); + return PTR_ERR(info->id_gpiod); + } + + ret = gpiod_set_debounce(info->id_gpiod, + USB_GPIO_DEBOUNCE_MS * 1000); + if (ret < 0) + info->debounce_jiffies = msecs_to_jiffies(USB_GPIO_DEBOUNCE_MS); + + INIT_DELAYED_WORK(&info->wq_detcable, usb_extcon_detect_cable); + + info->id_irq = gpiod_to_irq(info->id_gpiod); + if (info->id_irq < 0) { + dev_err(dev, "failed to get ID IRQ\n"); + return info->id_irq; + } + + ret = devm_request_threaded_irq(dev, info->id_irq, NULL, + usb_irq_handler, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + pdev->name, info); + if (ret < 0) { + dev_err(dev, "failed to request handler for ID IRQ\n"); + return ret; + } + + info->edev = devm_extcon_dev_allocate(dev, usb_extcon_cable); + if (IS_ERR(info->edev)) { + dev_err(dev, "failed to allocate extcon device\n"); + return -ENOMEM; + } + + ret = devm_extcon_dev_register(dev, info->edev); + if (ret < 0) { + dev_err(dev, "failed to register extcon device\n"); + return ret; + } + + platform_set_drvdata(pdev, info); + device_init_wakeup(dev, 1); + + /* Perform initial detection */ + usb_extcon_detect_cable(&info->wq_detcable.work); + + return 0; +} + +static int usb_extcon_remove(struct platform_device *pdev) +{ + struct usb_extcon_info *info = platform_get_drvdata(pdev); + + cancel_delayed_work_sync(&info->wq_detcable); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int usb_extcon_suspend(struct device *dev) +{ + struct usb_extcon_info *info = dev_get_drvdata(dev); + int ret = 0; + + if (device_may_wakeup(dev)) { + ret = enable_irq_wake(info->id_irq); + if (ret) + return ret; + } + + /* + * We don't want to process any IRQs after this point + * as GPIOs used behind I2C subsystem might not be + * accessible until resume completes. So disable IRQ. + */ + disable_irq(info->id_irq); + + return ret; +} + +static int usb_extcon_resume(struct device *dev) +{ + struct usb_extcon_info *info = dev_get_drvdata(dev); + int ret = 0; + + if (device_may_wakeup(dev)) { + ret = disable_irq_wake(info->id_irq); + if (ret) + return ret; + } + + enable_irq(info->id_irq); + + return ret; +} +#endif + +static SIMPLE_DEV_PM_OPS(usb_extcon_pm_ops, + usb_extcon_suspend, usb_extcon_resume); + +static struct of_device_id usb_extcon_dt_match[] = { + { .compatible = "linux,extcon-usb-gpio", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, usb_extcon_dt_match); + +static struct platform_driver usb_extcon_driver = { + .probe = usb_extcon_probe, + .remove = usb_extcon_remove, + .driver = { + .name = "extcon-usb-gpio", + .pm = &usb_extcon_pm_ops, + .of_match_table = usb_extcon_dt_match, + }, +}; + +module_platform_driver(usb_extcon_driver); + +MODULE_AUTHOR("Roger Quadros "); +MODULE_DESCRIPTION("USB GPIO extcon driver"); +MODULE_LICENSE("GPL v2"); -- cgit v0.10.2 From 27a28d32b4f22a4ae687837aeda6afb42116cca4 Mon Sep 17 00:00:00 2001 From: Jaewon Kim Date: Wed, 4 Feb 2015 13:56:07 +0900 Subject: extcon: max77843: Add max77843 MUIC driver This patch adds MAX77843 extcon driver to support for MUIC(Micro USB Interface Controller) device by using EXTCON subsystem to handle various external connectors. Signed-off-by: Jaewon Kim Signed-off-by: Chanwoo Choi diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig index e4c01ab..fdc0bf0 100644 --- a/drivers/extcon/Kconfig +++ b/drivers/extcon/Kconfig @@ -55,6 +55,16 @@ config EXTCON_MAX77693 Maxim MAX77693 PMIC. The MAX77693 MUIC is a USB port accessory detector and switch. +config EXTCON_MAX77843 + tristate "MAX77843 EXTCON Support" + depends on MFD_MAX77843 + select IRQ_DOMAIN + select REGMAP_I2C + help + If you say yes here you get support for the MUIC device of + Maxim MAX77843. The MAX77843 MUIC is a USB port accessory + detector add switch. + config EXTCON_MAX8997 tristate "MAX8997 EXTCON Support" depends on MFD_MAX8997 && IRQ_DOMAIN diff --git a/drivers/extcon/Makefile b/drivers/extcon/Makefile index 6a08a98..e1eb2d5 100644 --- a/drivers/extcon/Makefile +++ b/drivers/extcon/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_EXTCON_ARIZONA) += extcon-arizona.o obj-$(CONFIG_EXTCON_GPIO) += extcon-gpio.o obj-$(CONFIG_EXTCON_MAX14577) += extcon-max14577.o obj-$(CONFIG_EXTCON_MAX77693) += extcon-max77693.o +obj-$(CONFIG_EXTCON_MAX77843) += extcon-max77843.o obj-$(CONFIG_EXTCON_MAX8997) += extcon-max8997.o obj-$(CONFIG_EXTCON_PALMAS) += extcon-palmas.o obj-$(CONFIG_EXTCON_RT8973A) += extcon-rt8973a.o diff --git a/drivers/extcon/extcon-max77843.c b/drivers/extcon/extcon-max77843.c new file mode 100644 index 0000000..598a017 --- /dev/null +++ b/drivers/extcon/extcon-max77843.c @@ -0,0 +1,881 @@ +/* + * extcon-max77843.c - Maxim MAX77843 extcon driver to support + * MUIC(Micro USB Interface Controller) + * + * Copyright (C) 2015 Samsung Electronics + * Author: Jaewon Kim + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define DELAY_MS_DEFAULT 15000 /* unit: millisecond */ + +enum max77843_muic_status { + MAX77843_MUIC_STATUS1 = 0, + MAX77843_MUIC_STATUS2, + MAX77843_MUIC_STATUS3, + + MAX77843_MUIC_STATUS_NUM, +}; + +struct max77843_muic_info { + struct device *dev; + struct max77843 *max77843; + struct extcon_dev *edev; + + struct mutex mutex; + struct work_struct irq_work; + struct delayed_work wq_detcable; + + u8 status[MAX77843_MUIC_STATUS_NUM]; + int prev_cable_type; + int prev_chg_type; + int prev_gnd_type; + + bool irq_adc; + bool irq_chg; +}; + +enum max77843_muic_cable_group { + MAX77843_CABLE_GROUP_ADC = 0, + MAX77843_CABLE_GROUP_ADC_GND, + MAX77843_CABLE_GROUP_CHG, +}; + +enum max77843_muic_adc_debounce_time { + MAX77843_DEBOUNCE_TIME_5MS = 0, + MAX77843_DEBOUNCE_TIME_10MS, + MAX77843_DEBOUNCE_TIME_25MS, + MAX77843_DEBOUNCE_TIME_38_62MS, +}; + +/* Define accessory cable type */ +enum max77843_muic_accessory_type { + MAX77843_MUIC_ADC_GROUND = 0, + MAX77843_MUIC_ADC_SEND_END_BUTTON, + MAX77843_MUIC_ADC_REMOTE_S1_BUTTON, + MAX77843_MUIC_ADC_REMOTE_S2_BUTTON, + MAX77843_MUIC_ADC_REMOTE_S3_BUTTON, + MAX77843_MUIC_ADC_REMOTE_S4_BUTTON, + MAX77843_MUIC_ADC_REMOTE_S5_BUTTON, + MAX77843_MUIC_ADC_REMOTE_S6_BUTTON, + MAX77843_MUIC_ADC_REMOTE_S7_BUTTON, + MAX77843_MUIC_ADC_REMOTE_S8_BUTTON, + MAX77843_MUIC_ADC_REMOTE_S9_BUTTON, + MAX77843_MUIC_ADC_REMOTE_S10_BUTTON, + MAX77843_MUIC_ADC_REMOTE_S11_BUTTON, + MAX77843_MUIC_ADC_REMOTE_S12_BUTTON, + MAX77843_MUIC_ADC_RESERVED_ACC_1, + MAX77843_MUIC_ADC_RESERVED_ACC_2, + MAX77843_MUIC_ADC_RESERVED_ACC_3, + MAX77843_MUIC_ADC_RESERVED_ACC_4, + MAX77843_MUIC_ADC_RESERVED_ACC_5, + MAX77843_MUIC_ADC_AUDIO_DEVICE_TYPE2, + MAX77843_MUIC_ADC_PHONE_POWERED_DEV, + MAX77843_MUIC_ADC_TTY_CONVERTER, + MAX77843_MUIC_ADC_UART_CABLE, + MAX77843_MUIC_ADC_CEA936A_TYPE1_CHG, + MAX77843_MUIC_ADC_FACTORY_MODE_USB_OFF, + MAX77843_MUIC_ADC_FACTORY_MODE_USB_ON, + MAX77843_MUIC_ADC_AV_CABLE_NOLOAD, + MAX77843_MUIC_ADC_CEA936A_TYPE2_CHG, + MAX77843_MUIC_ADC_FACTORY_MODE_UART_OFF, + MAX77843_MUIC_ADC_FACTORY_MODE_UART_ON, + MAX77843_MUIC_ADC_AUDIO_DEVICE_TYPE1, + MAX77843_MUIC_ADC_OPEN, + + /* The blow accessories should check + not only ADC value but also ADC1K and VBVolt value. */ + /* Offset|ADC1K|VBVolt| */ + MAX77843_MUIC_GND_USB_HOST = 0x100, /* 0x1| 0| 0| */ + MAX77843_MUIC_GND_USB_HOST_VB = 0x101, /* 0x1| 0| 1| */ + MAX77843_MUIC_GND_MHL = 0x102, /* 0x1| 1| 0| */ + MAX77843_MUIC_GND_MHL_VB = 0x103, /* 0x1| 1| 1| */ +}; + +/* Define charger cable type */ +enum max77843_muic_charger_type { + MAX77843_MUIC_CHG_NONE = 0, + MAX77843_MUIC_CHG_USB, + MAX77843_MUIC_CHG_DOWNSTREAM, + MAX77843_MUIC_CHG_DEDICATED, + MAX77843_MUIC_CHG_SPECIAL_500MA, + MAX77843_MUIC_CHG_SPECIAL_1A, + MAX77843_MUIC_CHG_SPECIAL_BIAS, + MAX77843_MUIC_CHG_RESERVED, + MAX77843_MUIC_CHG_GND, +}; + +enum { + MAX77843_CABLE_USB = 0, + MAX77843_CABLE_USB_HOST, + MAX77843_CABLE_TA, + MAX77843_CABLE_CHARGE_DOWNSTREAM, + MAX77843_CABLE_FAST_CHARGER, + MAX77843_CABLE_SLOW_CHARGER, + MAX77843_CABLE_MHL, + MAX77843_CABLE_MHL_TA, + MAX77843_CABLE_JIG_USB_ON, + MAX77843_CABLE_JIG_USB_OFF, + MAX77843_CABLE_JIG_UART_ON, + MAX77843_CABLE_JIG_UART_OFF, + + MAX77843_CABLE_NUM, +}; + +static const char *max77843_extcon_cable[] = { + [MAX77843_CABLE_USB] = "USB", + [MAX77843_CABLE_USB_HOST] = "USB-HOST", + [MAX77843_CABLE_TA] = "TA", + [MAX77843_CABLE_CHARGE_DOWNSTREAM] = "CHARGER-DOWNSTREAM", + [MAX77843_CABLE_FAST_CHARGER] = "FAST-CHARGER", + [MAX77843_CABLE_SLOW_CHARGER] = "SLOW-CHARGER", + [MAX77843_CABLE_MHL] = "MHL", + [MAX77843_CABLE_MHL_TA] = "MHL-TA", + [MAX77843_CABLE_JIG_USB_ON] = "JIG-USB-ON", + [MAX77843_CABLE_JIG_USB_OFF] = "JIG-USB-OFF", + [MAX77843_CABLE_JIG_UART_ON] = "JIG-UART-ON", + [MAX77843_CABLE_JIG_UART_OFF] = "JIG-UART-OFF", +}; + +struct max77843_muic_irq { + unsigned int irq; + const char *name; + unsigned int virq; +}; + +static struct max77843_muic_irq max77843_muic_irqs[] = { + { MAX77843_MUIC_IRQ_INT1_ADC, "MUIC-ADC" }, + { MAX77843_MUIC_IRQ_INT1_ADCERROR, "MUIC-ADC_ERROR" }, + { MAX77843_MUIC_IRQ_INT1_ADC1K, "MUIC-ADC1K" }, + { MAX77843_MUIC_IRQ_INT2_CHGTYP, "MUIC-CHGTYP" }, + { MAX77843_MUIC_IRQ_INT2_CHGDETRUN, "MUIC-CHGDETRUN" }, + { MAX77843_MUIC_IRQ_INT2_DCDTMR, "MUIC-DCDTMR" }, + { MAX77843_MUIC_IRQ_INT2_DXOVP, "MUIC-DXOVP" }, + { MAX77843_MUIC_IRQ_INT2_VBVOLT, "MUIC-VBVOLT" }, + { MAX77843_MUIC_IRQ_INT3_VBADC, "MUIC-VBADC" }, + { MAX77843_MUIC_IRQ_INT3_VDNMON, "MUIC-VDNMON" }, + { MAX77843_MUIC_IRQ_INT3_DNRES, "MUIC-DNRES" }, + { MAX77843_MUIC_IRQ_INT3_MPNACK, "MUIC-MPNACK"}, + { MAX77843_MUIC_IRQ_INT3_MRXBUFOW, "MUIC-MRXBUFOW"}, + { MAX77843_MUIC_IRQ_INT3_MRXTRF, "MUIC-MRXTRF"}, + { MAX77843_MUIC_IRQ_INT3_MRXPERR, "MUIC-MRXPERR"}, + { MAX77843_MUIC_IRQ_INT3_MRXRDY, "MUIC-MRXRDY"}, +}; + +static const struct regmap_config max77843_muic_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = MAX77843_MUIC_REG_END, +}; + +static const struct regmap_irq max77843_muic_irq[] = { + /* INT1 interrupt */ + { .reg_offset = 0, .mask = MAX77843_MUIC_ADC, }, + { .reg_offset = 0, .mask = MAX77843_MUIC_ADCERROR, }, + { .reg_offset = 0, .mask = MAX77843_MUIC_ADC1K, }, + + /* INT2 interrupt */ + { .reg_offset = 1, .mask = MAX77843_MUIC_CHGTYP, }, + { .reg_offset = 1, .mask = MAX77843_MUIC_CHGDETRUN, }, + { .reg_offset = 1, .mask = MAX77843_MUIC_DCDTMR, }, + { .reg_offset = 1, .mask = MAX77843_MUIC_DXOVP, }, + { .reg_offset = 1, .mask = MAX77843_MUIC_VBVOLT, }, + + /* INT3 interrupt */ + { .reg_offset = 2, .mask = MAX77843_MUIC_VBADC, }, + { .reg_offset = 2, .mask = MAX77843_MUIC_VDNMON, }, + { .reg_offset = 2, .mask = MAX77843_MUIC_DNRES, }, + { .reg_offset = 2, .mask = MAX77843_MUIC_MPNACK, }, + { .reg_offset = 2, .mask = MAX77843_MUIC_MRXBUFOW, }, + { .reg_offset = 2, .mask = MAX77843_MUIC_MRXTRF, }, + { .reg_offset = 2, .mask = MAX77843_MUIC_MRXPERR, }, + { .reg_offset = 2, .mask = MAX77843_MUIC_MRXRDY, }, +}; + +static const struct regmap_irq_chip max77843_muic_irq_chip = { + .name = "max77843-muic", + .status_base = MAX77843_MUIC_REG_INT1, + .mask_base = MAX77843_MUIC_REG_INTMASK1, + .mask_invert = true, + .num_regs = 3, + .irqs = max77843_muic_irq, + .num_irqs = ARRAY_SIZE(max77843_muic_irq), +}; + +static int max77843_muic_set_path(struct max77843_muic_info *info, + u8 val, bool attached) +{ + struct max77843 *max77843 = info->max77843; + int ret = 0; + unsigned int ctrl1, ctrl2; + + if (attached) + ctrl1 = val; + else + ctrl1 = CONTROL1_SW_OPEN; + + ret = regmap_update_bits(max77843->regmap_muic, + MAX77843_MUIC_REG_CONTROL1, + CONTROL1_COM_SW, ctrl1); + if (ret < 0) { + dev_err(info->dev, "Cannot switch MUIC port\n"); + return ret; + } + + if (attached) + ctrl2 = MAX77843_MUIC_CONTROL2_CPEN_MASK; + else + ctrl2 = MAX77843_MUIC_CONTROL2_LOWPWR_MASK; + + ret = regmap_update_bits(max77843->regmap_muic, + MAX77843_MUIC_REG_CONTROL2, + MAX77843_MUIC_CONTROL2_LOWPWR_MASK | + MAX77843_MUIC_CONTROL2_CPEN_MASK, ctrl2); + if (ret < 0) { + dev_err(info->dev, "Cannot update lowpower mode\n"); + return ret; + } + + dev_dbg(info->dev, + "CONTROL1 : 0x%02x, CONTROL2 : 0x%02x, state : %s\n", + ctrl1, ctrl2, attached ? "attached" : "detached"); + + return 0; +} + +static int max77843_muic_get_cable_type(struct max77843_muic_info *info, + enum max77843_muic_cable_group group, bool *attached) +{ + int adc, chg_type, cable_type, gnd_type; + + adc = info->status[MAX77843_MUIC_STATUS1] & + MAX77843_MUIC_STATUS1_ADC_MASK; + adc >>= STATUS1_ADC_SHIFT; + + switch (group) { + case MAX77843_CABLE_GROUP_ADC: + if (adc == MAX77843_MUIC_ADC_OPEN) { + *attached = false; + cable_type = info->prev_cable_type; + info->prev_cable_type = MAX77843_MUIC_ADC_OPEN; + } else { + *attached = true; + cable_type = info->prev_cable_type = adc; + } + break; + case MAX77843_CABLE_GROUP_CHG: + chg_type = info->status[MAX77843_MUIC_STATUS2] & + MAX77843_MUIC_STATUS2_CHGTYP_MASK; + + /* Check GROUND accessory with charger cable */ + if (adc == MAX77843_MUIC_ADC_GROUND) { + if (chg_type == MAX77843_MUIC_CHG_NONE) { + /* The following state when charger cable is + * disconnected but the GROUND accessory still + * connected */ + *attached = false; + cable_type = info->prev_chg_type; + info->prev_chg_type = MAX77843_MUIC_CHG_NONE; + } else { + + /* The following state when charger cable is + * connected on the GROUND accessory */ + *attached = true; + cable_type = MAX77843_MUIC_CHG_GND; + info->prev_chg_type = MAX77843_MUIC_CHG_GND; + } + break; + } + + if (chg_type == MAX77843_MUIC_CHG_NONE) { + *attached = false; + cable_type = info->prev_chg_type; + info->prev_chg_type = MAX77843_MUIC_CHG_NONE; + } else { + *attached = true; + cable_type = info->prev_chg_type = chg_type; + } + break; + case MAX77843_CABLE_GROUP_ADC_GND: + if (adc == MAX77843_MUIC_ADC_OPEN) { + *attached = false; + cable_type = info->prev_gnd_type; + info->prev_gnd_type = MAX77843_MUIC_ADC_OPEN; + } else { + *attached = true; + + /* Offset|ADC1K|VBVolt| + * 0x1| 0| 0| USB-HOST + * 0x1| 0| 1| USB-HOST with VB + * 0x1| 1| 0| MHL + * 0x1| 1| 1| MHL with VB */ + /* Get ADC1K register bit */ + gnd_type = (info->status[MAX77843_MUIC_STATUS1] & + MAX77843_MUIC_STATUS1_ADC1K_MASK); + + /* Get VBVolt register bit */ + gnd_type |= (info->status[MAX77843_MUIC_STATUS2] & + MAX77843_MUIC_STATUS2_VBVOLT_MASK); + gnd_type >>= STATUS2_VBVOLT_SHIFT; + + /* Offset of GND cable */ + gnd_type |= MAX77843_MUIC_GND_USB_HOST; + cable_type = info->prev_gnd_type = gnd_type; + } + break; + default: + dev_err(info->dev, "Unknown cable group (%d)\n", group); + cable_type = -EINVAL; + break; + } + + return cable_type; +} + +static int max77843_muic_adc_gnd_handler(struct max77843_muic_info *info) +{ + int ret, gnd_cable_type; + bool attached; + + gnd_cable_type = max77843_muic_get_cable_type(info, + MAX77843_CABLE_GROUP_ADC_GND, &attached); + dev_dbg(info->dev, "external connector is %s (gnd:0x%02x)\n", + attached ? "attached" : "detached", gnd_cable_type); + + switch (gnd_cable_type) { + case MAX77843_MUIC_GND_USB_HOST: + case MAX77843_MUIC_GND_USB_HOST_VB: + ret = max77843_muic_set_path(info, CONTROL1_SW_USB, attached); + if (ret < 0) + return ret; + + extcon_set_cable_state(info->edev, "USB-HOST", attached); + break; + case MAX77843_MUIC_GND_MHL_VB: + case MAX77843_MUIC_GND_MHL: + ret = max77843_muic_set_path(info, CONTROL1_SW_OPEN, attached); + if (ret < 0) + return ret; + + extcon_set_cable_state(info->edev, "MHL", attached); + break; + default: + dev_err(info->dev, "failed to detect %s accessory(gnd:0x%x)\n", + attached ? "attached" : "detached", gnd_cable_type); + return -EINVAL; + } + + return 0; +} + +static int max77843_muic_jig_handler(struct max77843_muic_info *info, + int cable_type, bool attached) +{ + int ret; + + dev_dbg(info->dev, "external connector is %s (adc:0x%02x)\n", + attached ? "attached" : "detached", cable_type); + + switch (cable_type) { + case MAX77843_MUIC_ADC_FACTORY_MODE_USB_OFF: + ret = max77843_muic_set_path(info, CONTROL1_SW_USB, attached); + if (ret < 0) + return ret; + extcon_set_cable_state(info->edev, "JIG-USB-OFF", attached); + break; + case MAX77843_MUIC_ADC_FACTORY_MODE_USB_ON: + ret = max77843_muic_set_path(info, CONTROL1_SW_USB, attached); + if (ret < 0) + return ret; + extcon_set_cable_state(info->edev, "JIG-USB-ON", attached); + break; + case MAX77843_MUIC_ADC_FACTORY_MODE_UART_OFF: + ret = max77843_muic_set_path(info, CONTROL1_SW_UART, attached); + if (ret < 0) + return ret; + extcon_set_cable_state(info->edev, "JIG-UART-OFF", attached); + break; + default: + ret = max77843_muic_set_path(info, CONTROL1_SW_OPEN, attached); + if (ret < 0) + return ret; + break; + } + + return 0; +} + +static int max77843_muic_adc_handler(struct max77843_muic_info *info) +{ + int ret, cable_type; + bool attached; + + cable_type = max77843_muic_get_cable_type(info, + MAX77843_CABLE_GROUP_ADC, &attached); + + dev_dbg(info->dev, + "external connector is %s (adc:0x%02x, prev_adc:0x%x)\n", + attached ? "attached" : "detached", cable_type, + info->prev_cable_type); + + switch (cable_type) { + case MAX77843_MUIC_ADC_GROUND: + ret = max77843_muic_adc_gnd_handler(info); + if (ret < 0) + return ret; + break; + case MAX77843_MUIC_ADC_FACTORY_MODE_USB_OFF: + case MAX77843_MUIC_ADC_FACTORY_MODE_USB_ON: + case MAX77843_MUIC_ADC_FACTORY_MODE_UART_OFF: + ret = max77843_muic_jig_handler(info, cable_type, attached); + if (ret < 0) + return ret; + break; + case MAX77843_MUIC_ADC_SEND_END_BUTTON: + case MAX77843_MUIC_ADC_REMOTE_S1_BUTTON: + case MAX77843_MUIC_ADC_REMOTE_S2_BUTTON: + case MAX77843_MUIC_ADC_REMOTE_S3_BUTTON: + case MAX77843_MUIC_ADC_REMOTE_S4_BUTTON: + case MAX77843_MUIC_ADC_REMOTE_S5_BUTTON: + case MAX77843_MUIC_ADC_REMOTE_S6_BUTTON: + case MAX77843_MUIC_ADC_REMOTE_S7_BUTTON: + case MAX77843_MUIC_ADC_REMOTE_S8_BUTTON: + case MAX77843_MUIC_ADC_REMOTE_S9_BUTTON: + case MAX77843_MUIC_ADC_REMOTE_S10_BUTTON: + case MAX77843_MUIC_ADC_REMOTE_S11_BUTTON: + case MAX77843_MUIC_ADC_REMOTE_S12_BUTTON: + case MAX77843_MUIC_ADC_RESERVED_ACC_1: + case MAX77843_MUIC_ADC_RESERVED_ACC_2: + case MAX77843_MUIC_ADC_RESERVED_ACC_3: + case MAX77843_MUIC_ADC_RESERVED_ACC_4: + case MAX77843_MUIC_ADC_RESERVED_ACC_5: + case MAX77843_MUIC_ADC_AUDIO_DEVICE_TYPE2: + case MAX77843_MUIC_ADC_PHONE_POWERED_DEV: + case MAX77843_MUIC_ADC_TTY_CONVERTER: + case MAX77843_MUIC_ADC_UART_CABLE: + case MAX77843_MUIC_ADC_CEA936A_TYPE1_CHG: + case MAX77843_MUIC_ADC_AV_CABLE_NOLOAD: + case MAX77843_MUIC_ADC_CEA936A_TYPE2_CHG: + case MAX77843_MUIC_ADC_FACTORY_MODE_UART_ON: + case MAX77843_MUIC_ADC_AUDIO_DEVICE_TYPE1: + case MAX77843_MUIC_ADC_OPEN: + dev_err(info->dev, + "accessory is %s but it isn't used (adc:0x%x)\n", + attached ? "attached" : "detached", cable_type); + return -EAGAIN; + default: + dev_err(info->dev, + "failed to detect %s accessory (adc:0x%x)\n", + attached ? "attached" : "detached", cable_type); + return -EINVAL; + } + + return 0; +} + +static int max77843_muic_chg_handler(struct max77843_muic_info *info) +{ + int ret, chg_type, gnd_type; + bool attached; + + chg_type = max77843_muic_get_cable_type(info, + MAX77843_CABLE_GROUP_CHG, &attached); + + dev_dbg(info->dev, + "external connector is %s(chg_type:0x%x, prev_chg_type:0x%x)\n", + attached ? "attached" : "detached", + chg_type, info->prev_chg_type); + + switch (chg_type) { + case MAX77843_MUIC_CHG_USB: + ret = max77843_muic_set_path(info, CONTROL1_SW_USB, attached); + if (ret < 0) + return ret; + + extcon_set_cable_state(info->edev, "USB", attached); + break; + case MAX77843_MUIC_CHG_DOWNSTREAM: + ret = max77843_muic_set_path(info, CONTROL1_SW_OPEN, attached); + if (ret < 0) + return ret; + + extcon_set_cable_state(info->edev, + "CHARGER-DOWNSTREAM", attached); + break; + case MAX77843_MUIC_CHG_DEDICATED: + ret = max77843_muic_set_path(info, CONTROL1_SW_OPEN, attached); + if (ret < 0) + return ret; + + extcon_set_cable_state(info->edev, "TA", attached); + break; + case MAX77843_MUIC_CHG_SPECIAL_500MA: + ret = max77843_muic_set_path(info, CONTROL1_SW_OPEN, attached); + if (ret < 0) + return ret; + + extcon_set_cable_state(info->edev, "SLOW-CHAREGER", attached); + break; + case MAX77843_MUIC_CHG_SPECIAL_1A: + ret = max77843_muic_set_path(info, CONTROL1_SW_OPEN, attached); + if (ret < 0) + return ret; + + extcon_set_cable_state(info->edev, "FAST-CHARGER", attached); + break; + case MAX77843_MUIC_CHG_GND: + gnd_type = max77843_muic_get_cable_type(info, + MAX77843_CABLE_GROUP_ADC_GND, &attached); + + /* Charger cable on MHL accessory is attach or detach */ + if (gnd_type == MAX77843_MUIC_GND_MHL_VB) + extcon_set_cable_state(info->edev, "MHL-TA", true); + else if (gnd_type == MAX77843_MUIC_GND_MHL) + extcon_set_cable_state(info->edev, "MHL-TA", false); + break; + case MAX77843_MUIC_CHG_NONE: + break; + default: + dev_err(info->dev, + "failed to detect %s accessory (chg_type:0x%x)\n", + attached ? "attached" : "detached", chg_type); + + max77843_muic_set_path(info, CONTROL1_SW_OPEN, attached); + return -EINVAL; + } + + return 0; +} + +static void max77843_muic_irq_work(struct work_struct *work) +{ + struct max77843_muic_info *info = container_of(work, + struct max77843_muic_info, irq_work); + struct max77843 *max77843 = info->max77843; + int ret = 0; + + mutex_lock(&info->mutex); + + ret = regmap_bulk_read(max77843->regmap_muic, + MAX77843_MUIC_REG_STATUS1, info->status, + MAX77843_MUIC_STATUS_NUM); + if (ret) { + dev_err(info->dev, "Cannot read STATUS registers\n"); + mutex_unlock(&info->mutex); + return; + } + + if (info->irq_adc) { + ret = max77843_muic_adc_handler(info); + if (ret) + dev_err(info->dev, "Unknown cable type\n"); + info->irq_adc = false; + } + + if (info->irq_chg) { + ret = max77843_muic_chg_handler(info); + if (ret) + dev_err(info->dev, "Unknown charger type\n"); + info->irq_chg = false; + } + + mutex_unlock(&info->mutex); +} + +static irqreturn_t max77843_muic_irq_handler(int irq, void *data) +{ + struct max77843_muic_info *info = data; + int i, irq_type = -1; + + for (i = 0; i < ARRAY_SIZE(max77843_muic_irqs); i++) + if (irq == max77843_muic_irqs[i].virq) + irq_type = max77843_muic_irqs[i].irq; + + switch (irq_type) { + case MAX77843_MUIC_IRQ_INT1_ADC: + case MAX77843_MUIC_IRQ_INT1_ADCERROR: + case MAX77843_MUIC_IRQ_INT1_ADC1K: + info->irq_adc = true; + break; + case MAX77843_MUIC_IRQ_INT2_CHGTYP: + case MAX77843_MUIC_IRQ_INT2_CHGDETRUN: + case MAX77843_MUIC_IRQ_INT2_DCDTMR: + case MAX77843_MUIC_IRQ_INT2_DXOVP: + case MAX77843_MUIC_IRQ_INT2_VBVOLT: + info->irq_chg = true; + break; + case MAX77843_MUIC_IRQ_INT3_VBADC: + case MAX77843_MUIC_IRQ_INT3_VDNMON: + case MAX77843_MUIC_IRQ_INT3_DNRES: + case MAX77843_MUIC_IRQ_INT3_MPNACK: + case MAX77843_MUIC_IRQ_INT3_MRXBUFOW: + case MAX77843_MUIC_IRQ_INT3_MRXTRF: + case MAX77843_MUIC_IRQ_INT3_MRXPERR: + case MAX77843_MUIC_IRQ_INT3_MRXRDY: + break; + default: + dev_err(info->dev, "Cannot recognize IRQ(%d)\n", irq_type); + break; + } + + schedule_work(&info->irq_work); + + return IRQ_HANDLED; +} + +static void max77843_muic_detect_cable_wq(struct work_struct *work) +{ + struct max77843_muic_info *info = container_of(to_delayed_work(work), + struct max77843_muic_info, wq_detcable); + struct max77843 *max77843 = info->max77843; + int chg_type, adc, ret; + bool attached; + + mutex_lock(&info->mutex); + + ret = regmap_bulk_read(max77843->regmap_muic, + MAX77843_MUIC_REG_STATUS1, info->status, + MAX77843_MUIC_STATUS_NUM); + if (ret) { + dev_err(info->dev, "Cannot read STATUS registers\n"); + goto err_cable_wq; + } + + adc = max77843_muic_get_cable_type(info, + MAX77843_CABLE_GROUP_ADC, &attached); + if (attached && adc != MAX77843_MUIC_ADC_OPEN) { + ret = max77843_muic_adc_handler(info); + if (ret < 0) { + dev_err(info->dev, "Cannot detect accessory\n"); + goto err_cable_wq; + } + } + + chg_type = max77843_muic_get_cable_type(info, + MAX77843_CABLE_GROUP_CHG, &attached); + if (attached && chg_type != MAX77843_MUIC_CHG_NONE) { + ret = max77843_muic_chg_handler(info); + if (ret < 0) { + dev_err(info->dev, "Cannot detect charger accessory\n"); + goto err_cable_wq; + } + } + +err_cable_wq: + mutex_unlock(&info->mutex); +} + +static int max77843_muic_set_debounce_time(struct max77843_muic_info *info, + enum max77843_muic_adc_debounce_time time) +{ + struct max77843 *max77843 = info->max77843; + unsigned int ret; + + switch (time) { + case MAX77843_DEBOUNCE_TIME_5MS: + case MAX77843_DEBOUNCE_TIME_10MS: + case MAX77843_DEBOUNCE_TIME_25MS: + case MAX77843_DEBOUNCE_TIME_38_62MS: + ret = regmap_update_bits(max77843->regmap_muic, + MAX77843_MUIC_REG_CONTROL4, + MAX77843_MUIC_CONTROL4_ADCDBSET_MASK, + time << CONTROL4_ADCDBSET_SHIFT); + if (ret < 0) { + dev_err(info->dev, "Cannot write MUIC regmap\n"); + return ret; + } + break; + default: + dev_err(info->dev, "Invalid ADC debounce time\n"); + return -EINVAL; + } + + return 0; +} + +static int max77843_init_muic_regmap(struct max77843 *max77843) +{ + int ret; + + max77843->i2c_muic = i2c_new_dummy(max77843->i2c->adapter, + I2C_ADDR_MUIC); + if (!max77843->i2c_muic) { + dev_err(&max77843->i2c->dev, + "Cannot allocate I2C device for MUIC\n"); + return PTR_ERR(max77843->i2c_muic); + } + + i2c_set_clientdata(max77843->i2c_muic, max77843); + + max77843->regmap_muic = devm_regmap_init_i2c(max77843->i2c_muic, + &max77843_muic_regmap_config); + if (IS_ERR(max77843->regmap_muic)) { + ret = PTR_ERR(max77843->regmap_muic); + goto err_muic_i2c; + } + + ret = regmap_add_irq_chip(max77843->regmap_muic, max77843->irq, + IRQF_TRIGGER_LOW | IRQF_ONESHOT | IRQF_SHARED, + 0, &max77843_muic_irq_chip, &max77843->irq_data_muic); + if (ret < 0) { + dev_err(&max77843->i2c->dev, "Cannot add MUIC IRQ chip\n"); + goto err_muic_i2c; + } + + return 0; + +err_muic_i2c: + i2c_unregister_device(max77843->i2c_muic); + + return ret; +} + +static int max77843_muic_probe(struct platform_device *pdev) +{ + struct max77843 *max77843 = dev_get_drvdata(pdev->dev.parent); + struct max77843_muic_info *info; + unsigned int id; + int i, ret; + + info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->dev = &pdev->dev; + info->max77843 = max77843; + + platform_set_drvdata(pdev, info); + mutex_init(&info->mutex); + + /* Initialize i2c and regmap */ + ret = max77843_init_muic_regmap(max77843); + if (ret) { + dev_err(&pdev->dev, "Failed to init MUIC regmap\n"); + return ret; + } + + /* Turn off auto detection configuration */ + ret = regmap_update_bits(max77843->regmap_muic, + MAX77843_MUIC_REG_CONTROL4, + MAX77843_MUIC_CONTROL4_USBAUTO_MASK | + MAX77843_MUIC_CONTROL4_FCTAUTO_MASK, + CONTROL4_AUTO_DISABLE); + + /* Initialize extcon device */ + info->edev = devm_extcon_dev_allocate(&pdev->dev, + max77843_extcon_cable); + if (IS_ERR(info->edev)) { + dev_err(&pdev->dev, "Failed to allocate memory for extcon\n"); + ret = -ENODEV; + goto err_muic_irq; + } + + ret = devm_extcon_dev_register(&pdev->dev, info->edev); + if (ret) { + dev_err(&pdev->dev, "Failed to register extcon device\n"); + goto err_muic_irq; + } + + /* Set ADC debounce time */ + max77843_muic_set_debounce_time(info, MAX77843_DEBOUNCE_TIME_25MS); + + /* Set initial path for UART */ + max77843_muic_set_path(info, CONTROL1_SW_UART, true); + + /* Check revision number of MUIC device */ + ret = regmap_read(max77843->regmap_muic, MAX77843_MUIC_REG_ID, &id); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to read revision number\n"); + goto err_muic_irq; + } + dev_info(info->dev, "MUIC device ID : 0x%x\n", id); + + /* Support virtual irq domain for max77843 MUIC device */ + INIT_WORK(&info->irq_work, max77843_muic_irq_work); + + for (i = 0; i < ARRAY_SIZE(max77843_muic_irqs); i++) { + struct max77843_muic_irq *muic_irq = &max77843_muic_irqs[i]; + unsigned int virq = 0; + + virq = regmap_irq_get_virq(max77843->irq_data_muic, + muic_irq->irq); + if (virq <= 0) { + ret = -EINVAL; + goto err_muic_irq; + } + muic_irq->virq = virq; + + ret = devm_request_threaded_irq(&pdev->dev, virq, NULL, + max77843_muic_irq_handler, IRQF_NO_SUSPEND, + muic_irq->name, info); + if (ret) { + dev_err(&pdev->dev, + "Failed to request irq (IRQ: %d, error: %d)\n", + muic_irq->irq, ret); + goto err_muic_irq; + } + } + + /* Detect accessory after completing the initialization of platform */ + INIT_DELAYED_WORK(&info->wq_detcable, max77843_muic_detect_cable_wq); + queue_delayed_work(system_power_efficient_wq, + &info->wq_detcable, msecs_to_jiffies(DELAY_MS_DEFAULT)); + + return 0; + +err_muic_irq: + regmap_del_irq_chip(max77843->irq, max77843->irq_data_muic); + i2c_unregister_device(max77843->i2c_muic); + + return ret; +} + +static int max77843_muic_remove(struct platform_device *pdev) +{ + struct max77843_muic_info *info = platform_get_drvdata(pdev); + struct max77843 *max77843 = info->max77843; + + cancel_work_sync(&info->irq_work); + regmap_del_irq_chip(max77843->irq, max77843->irq_data_muic); + i2c_unregister_device(max77843->i2c_muic); + + return 0; +} + +static const struct platform_device_id max77843_muic_id[] = { + { "max77843-muic", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(platform, max77843_muic_id); + +static struct platform_driver max77843_muic_driver = { + .driver = { + .name = "max77843-muic", + }, + .probe = max77843_muic_probe, + .remove = max77843_muic_remove, + .id_table = max77843_muic_id, +}; + +static int __init max77843_muic_init(void) +{ + return platform_driver_register(&max77843_muic_driver); +} +subsys_initcall(max77843_muic_init); + +MODULE_DESCRIPTION("Maxim MAX77843 Extcon driver"); +MODULE_AUTHOR("Jaewon Kim "); +MODULE_LICENSE("GPL"); -- cgit v0.10.2 From e513229b4c386e6c9f66298c13fde92f73e6e1ac Mon Sep 17 00:00:00 2001 From: Vitaly Kuznetsov Date: Fri, 27 Feb 2015 11:25:51 -0800 Subject: Drivers: hv: vmbus: prevent cpu offlining on newer hypervisors When an SMP Hyper-V guest is running on top of 2012R2 Server and secondary cpus are sent offline (with echo 0 > /sys/devices/system/cpu/cpu$cpu/online) the system freeze is observed. This happens due to the fact that on newer hypervisors (Win8, WS2012R2, ...) vmbus channel handlers are distributed across all cpus (see init_vp_index() function in drivers/hv/channel_mgmt.c) and on cpu offlining nobody reassigns them to CPU0. Prevent cpu offlining when vmbus is loaded until the issue is fixed host-side. This patch also disables hibernation but it is OK as it is also broken (MCE error is hit on resume). Suspend still works. Tested with WS2008R2 and WS2012R2. Signed-off-by: Vitaly Kuznetsov Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c index f518b8d7..3b18a66 100644 --- a/drivers/hv/vmbus_drv.c +++ b/drivers/hv/vmbus_drv.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -704,6 +705,39 @@ static void vmbus_isr(void) } } +#ifdef CONFIG_HOTPLUG_CPU +static int hyperv_cpu_disable(void) +{ + return -ENOSYS; +} + +static void hv_cpu_hotplug_quirk(bool vmbus_loaded) +{ + static void *previous_cpu_disable; + + /* + * Offlining a CPU when running on newer hypervisors (WS2012R2, Win8, + * ...) is not supported at this moment as channel interrupts are + * distributed across all of them. + */ + + if ((vmbus_proto_version == VERSION_WS2008) || + (vmbus_proto_version == VERSION_WIN7)) + return; + + if (vmbus_loaded) { + previous_cpu_disable = smp_ops.cpu_disable; + smp_ops.cpu_disable = hyperv_cpu_disable; + pr_notice("CPU offlining is not supported by hypervisor\n"); + } else if (previous_cpu_disable) + smp_ops.cpu_disable = previous_cpu_disable; +} +#else +static void hv_cpu_hotplug_quirk(bool vmbus_loaded) +{ +} +#endif + /* * vmbus_bus_init -Main vmbus driver initialization routine. * @@ -744,6 +778,7 @@ static int vmbus_bus_init(int irq) if (ret) goto err_alloc; + hv_cpu_hotplug_quirk(true); vmbus_request_offers(); return 0; @@ -997,6 +1032,7 @@ static void __exit vmbus_exit(void) bus_unregister(&hv_bus); hv_cleanup(); acpi_bus_unregister_driver(&vmbus_acpi_driver); + hv_cpu_hotplug_quirk(false); } -- cgit v0.10.2 From bc63b6f634d91a0b2a7f3ba4f266e55fec369de3 Mon Sep 17 00:00:00 2001 From: Vitaly Kuznetsov Date: Fri, 27 Feb 2015 11:25:52 -0800 Subject: Drivers: hv: vmbus: rename channel work queues All channel work queues are named 'hv_vmbus_ctl', this makes them indistinguishable in ps output and makes it hard to link to the corresponding vmbus device. Rename them to hv_vmbus_ctl/N and make vmbus device names match, e.g. now vmbus_1 device is served by hv_vmbus_ctl/1 work queue. Signed-off-by: Vitaly Kuznetsov Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/hv/channel_mgmt.c b/drivers/hv/channel_mgmt.c index 3736f71..ba4b25f 100644 --- a/drivers/hv/channel_mgmt.c +++ b/drivers/hv/channel_mgmt.c @@ -139,19 +139,22 @@ EXPORT_SYMBOL_GPL(vmbus_prep_negotiate_resp); */ static struct vmbus_channel *alloc_channel(void) { + static atomic_t chan_num = ATOMIC_INIT(0); struct vmbus_channel *channel; channel = kzalloc(sizeof(*channel), GFP_ATOMIC); if (!channel) return NULL; + channel->id = atomic_inc_return(&chan_num); spin_lock_init(&channel->inbound_lock); spin_lock_init(&channel->lock); INIT_LIST_HEAD(&channel->sc_list); INIT_LIST_HEAD(&channel->percpu_list); - channel->controlwq = create_workqueue("hv_vmbus_ctl"); + channel->controlwq = alloc_workqueue("hv_vmbus_ctl/%d", WQ_MEM_RECLAIM, + 1, channel->id); if (!channel->controlwq) { kfree(channel); return NULL; diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c index 3b18a66..e334ccc 100644 --- a/drivers/hv/vmbus_drv.c +++ b/drivers/hv/vmbus_drv.c @@ -875,10 +875,8 @@ int vmbus_device_register(struct hv_device *child_device_obj) { int ret = 0; - static atomic_t device_num = ATOMIC_INIT(0); - - dev_set_name(&child_device_obj->device, "vmbus_0_%d", - atomic_inc_return(&device_num)); + dev_set_name(&child_device_obj->device, "vmbus_%d", + child_device_obj->channel->id); child_device_obj->device.bus = &hv_bus; child_device_obj->device.parent = &hv_acpi_dev->dev; diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index 5a2ba67..26a32b7 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -646,6 +646,9 @@ struct hv_input_signal_event_buffer { }; struct vmbus_channel { + /* Unique channel id */ + int id; + struct list_head listentry; struct hv_device *device_obj; -- cgit v0.10.2 From adcde069a85cb6674bdcf5f49f434d18b2db6e58 Mon Sep 17 00:00:00 2001 From: Vitaly Kuznetsov Date: Fri, 27 Feb 2015 11:25:53 -0800 Subject: Drivers: hv: vmbus: avoid double kfree for device_obj On driver shutdown device_obj is being freed twice: 1) In vmbus_free_channels() 2) vmbus_device_release() (which is being triggered by device_unregister() in vmbus_device_unregister(). This double kfree leads to the following sporadic crash on driver unload: [ 23.469876] general protection fault: 0000 [#1] SMP [ 23.470036] Modules linked in: hv_vmbus(-) [ 23.470036] CPU: 2 PID: 213 Comm: rmmod Not tainted 3.19.0-rc5_bug923184+ #488 [ 23.470036] Hardware name: Microsoft Corporation Virtual Machine/Virtual Machine, BIOS 090006 05/23/2012 [ 23.470036] task: ffff880036ef1cb0 ti: ffff880036ce8000 task.ti: ffff880036ce8000 [ 23.470036] RIP: 0010:[] [] __kmalloc_node_track_caller+0xdb/0x1e0 [ 23.470036] RSP: 0018:ffff880036cebcc8 EFLAGS: 00010246 ... When this crash does not happen on driver unload the similar one is expected if we try to load hv_vmbus again. Remove kfree from vmbus_free_channels() as freeing it from vmbus_device_release() seems right. Signed-off-by: Vitaly Kuznetsov Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/hv/channel_mgmt.c b/drivers/hv/channel_mgmt.c index ba4b25f..36bacc7 100644 --- a/drivers/hv/channel_mgmt.c +++ b/drivers/hv/channel_mgmt.c @@ -262,7 +262,6 @@ void vmbus_free_channels(void) list_for_each_entry(channel, &vmbus_connection.chn_list, listentry) { vmbus_device_unregister(channel->device_obj); - kfree(channel->device_obj); free_channel(channel); } } -- cgit v0.10.2 From 09a196288ec4617a920e051af6651ce03968c8b9 Mon Sep 17 00:00:00 2001 From: Vitaly Kuznetsov Date: Fri, 27 Feb 2015 11:25:54 -0800 Subject: Drivers: hv: vmbus: teardown hv_vmbus_con workqueue and vmbus_connection pages on shutdown We need to destroy hv_vmbus_con on module shutdown, otherwise the following crash is sometimes observed: [ 76.569845] hv_vmbus: Hyper-V Host Build:9600-6.3-17-0.17039; Vmbus version:3.0 [ 82.598859] BUG: unable to handle kernel paging request at ffffffffa0003480 [ 82.599287] IP: [] 0xffffffffa0003480 [ 82.599287] PGD 1f34067 PUD 1f35063 PMD 3f72d067 PTE 0 [ 82.599287] Oops: 0010 [#1] SMP [ 82.599287] Modules linked in: [last unloaded: hv_vmbus] [ 82.599287] CPU: 0 PID: 26 Comm: kworker/0:1 Not tainted 3.19.0-rc5_bug923184+ #488 [ 82.599287] Hardware name: Microsoft Corporation Virtual Machine/Virtual Machine, BIOS Hyper-V UEFI Release v1.0 11/26/2012 [ 82.599287] Workqueue: hv_vmbus_con 0xffffffffa0003480 [ 82.599287] task: ffff88007b6ddfa0 ti: ffff88007f8f8000 task.ti: ffff88007f8f8000 [ 82.599287] RIP: 0010:[] [] 0xffffffffa0003480 [ 82.599287] RSP: 0018:ffff88007f8fbe00 EFLAGS: 00010202 ... To avoid memory leaks we need to free monitor_pages and int_page for vmbus_connection. Implement vmbus_disconnect() function by separating cleanup path from vmbus_connect(). As we use hv_vmbus_con to release channels (see free_channel() in channel_mgmt.c) we need to make sure the work was done before we remove the queue, do that with drain_workqueue(). We also need to avoid handling messages which can (potentially) create new channels, so set vmbus_connection.conn_state = DISCONNECTED at the very beginning of vmbus_exit() and check for that in vmbus_onmessage_work(). Signed-off-by: Vitaly Kuznetsov Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/hv/connection.c b/drivers/hv/connection.c index a63a795..c4acd1c 100644 --- a/drivers/hv/connection.c +++ b/drivers/hv/connection.c @@ -216,10 +216,21 @@ int vmbus_connect(void) cleanup: pr_err("Unable to connect to host\n"); + vmbus_connection.conn_state = DISCONNECTED; + vmbus_disconnect(); + + kfree(msginfo); + + return ret; +} - if (vmbus_connection.work_queue) +void vmbus_disconnect(void) +{ + if (vmbus_connection.work_queue) { + drain_workqueue(vmbus_connection.work_queue); destroy_workqueue(vmbus_connection.work_queue); + } if (vmbus_connection.int_page) { free_pages((unsigned long)vmbus_connection.int_page, 0); @@ -230,10 +241,6 @@ cleanup: free_pages((unsigned long)vmbus_connection.monitor_pages[1], 0); vmbus_connection.monitor_pages[0] = NULL; vmbus_connection.monitor_pages[1] = NULL; - - kfree(msginfo); - - return ret; } /* diff --git a/drivers/hv/hyperv_vmbus.h b/drivers/hv/hyperv_vmbus.h index 44b1c94..6cf2de9 100644 --- a/drivers/hv/hyperv_vmbus.h +++ b/drivers/hv/hyperv_vmbus.h @@ -692,6 +692,7 @@ void vmbus_free_channels(void); /* Connection interface */ int vmbus_connect(void); +void vmbus_disconnect(void); int vmbus_post_msg(void *buffer, size_t buflen); diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c index e334ccc..76db97d 100644 --- a/drivers/hv/vmbus_drv.c +++ b/drivers/hv/vmbus_drv.c @@ -574,6 +574,10 @@ static void vmbus_onmessage_work(struct work_struct *work) { struct onmessage_work_context *ctx; + /* Do not process messages if we're in DISCONNECTED state */ + if (vmbus_connection.conn_state == DISCONNECTED) + return; + ctx = container_of(work, struct onmessage_work_context, work); vmbus_onmessage(&ctx->msg); @@ -1025,12 +1029,14 @@ cleanup: static void __exit vmbus_exit(void) { + vmbus_connection.conn_state = DISCONNECTED; hv_remove_vmbus_irq(); vmbus_free_channels(); bus_unregister(&hv_bus); hv_cleanup(); acpi_bus_unregister_driver(&vmbus_acpi_driver); hv_cpu_hotplug_quirk(false); + vmbus_disconnect(); } -- cgit v0.10.2 From e72e7ac583ee8adddbc668cdefde7d7d3021be79 Mon Sep 17 00:00:00 2001 From: Vitaly Kuznetsov Date: Fri, 27 Feb 2015 11:25:55 -0800 Subject: drivers: hv: vmbus: Teardown synthetic interrupt controllers on module unload SynIC has to be switched off when we unload the module, otherwise registered memory pages can get corrupted after (as Hyper-V host still writes there) and we see the following crashes for random processes: [ 89.116774] BUG: Bad page map in process sh pte:4989c716 pmd:36f81067 [ 89.159454] addr:0000000000437000 vm_flags:00000875 anon_vma: (null) mapping:ffff88007bba55a0 index:37 [ 89.226146] vma->vm_ops->fault: filemap_fault+0x0/0x410 [ 89.257776] vma->vm_file->f_op->mmap: generic_file_mmap+0x0/0x60 [ 89.297570] CPU: 0 PID: 215 Comm: sh Tainted: G B 3.19.0-rc5_bug923184+ #488 [ 89.353738] Hardware name: Microsoft Corporation Virtual Machine/Virtual Machine, BIOS 090006 05/23/2012 [ 89.409138] 0000000000000000 000000004e083d7b ffff880036e9fa18 ffffffff81a68d31 [ 89.468724] 0000000000000000 0000000000437000 ffff880036e9fa68 ffffffff811a1e3a [ 89.519233] 000000004989c716 0000000000000037 ffffea0001edc340 0000000000437000 [ 89.575751] Call Trace: [ 89.591060] [] dump_stack+0x45/0x57 [ 89.625164] [] print_bad_pte+0x1aa/0x250 [ 89.667234] [] vm_normal_page+0x55/0xa0 [ 89.703818] [] unmap_page_range+0x425/0x8a0 [ 89.737982] [] unmap_single_vma+0x81/0xf0 [ 89.780385] [] ? lru_deactivate_fn+0x190/0x190 [ 89.820130] [] unmap_vmas+0x51/0xa0 [ 89.860168] [] exit_mmap+0xac/0x1a0 [ 89.890588] [] mmput+0x63/0x100 [ 89.919205] [] flush_old_exec+0x3f8/0x8b0 [ 89.962135] [] load_elf_binary+0x32b/0x1260 [ 89.998581] [] ? get_user_pages+0x52/0x60 hv_synic_cleanup() function exists but noone calls it now. Do the following: - call hv_synic_cleanup() on each cpu from vmbus_exit(); - write global disable bit through MSR; - use hv_synic_free_cpu() to avoid memory leask and code duplication. Signed-off-by: Vitaly Kuznetsov Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/hv/hv.c b/drivers/hv/hv.c index 50e51a5..39531dc 100644 --- a/drivers/hv/hv.c +++ b/drivers/hv/hv.c @@ -477,6 +477,7 @@ void hv_synic_cleanup(void *arg) union hv_synic_sint shared_sint; union hv_synic_simp simp; union hv_synic_siefp siefp; + union hv_synic_scontrol sctrl; int cpu = smp_processor_id(); if (!hv_context.synic_initialized) @@ -502,6 +503,10 @@ void hv_synic_cleanup(void *arg) wrmsrl(HV_X64_MSR_SIEFP, siefp.as_uint64); - free_page((unsigned long)hv_context.synic_message_page[cpu]); - free_page((unsigned long)hv_context.synic_event_page[cpu]); + /* Disable the global synic bit */ + rdmsrl(HV_X64_MSR_SCONTROL, sctrl.as_uint64); + sctrl.enable = 0; + wrmsrl(HV_X64_MSR_SCONTROL, sctrl.as_uint64); + + hv_synic_free_cpu(cpu); } diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c index 76db97d..d8233d4 100644 --- a/drivers/hv/vmbus_drv.c +++ b/drivers/hv/vmbus_drv.c @@ -1029,11 +1029,15 @@ cleanup: static void __exit vmbus_exit(void) { + int cpu; + vmbus_connection.conn_state = DISCONNECTED; hv_remove_vmbus_irq(); vmbus_free_channels(); bus_unregister(&hv_bus); hv_cleanup(); + for_each_online_cpu(cpu) + smp_call_function_single(cpu, hv_synic_cleanup, NULL, 1); acpi_bus_unregister_driver(&vmbus_acpi_driver); hv_cpu_hotplug_quirk(false); vmbus_disconnect(); -- cgit v0.10.2 From 32a158325acf12842764b1681f53903673f2f22e Mon Sep 17 00:00:00 2001 From: Vitaly Kuznetsov Date: Fri, 27 Feb 2015 11:25:56 -0800 Subject: clockevents: export clockevents_unbind_device instead of clockevents_unbind It looks like clockevents_unbind is being exported by mistake as: - it is static; - it is not listed in include/linux/clockchips.h; - EXPORT_SYMBOL_GPL(clockevents_unbind) follows clockevents_unbind_device() implementation. I think clockevents_unbind_device should be exported instead. This is going to be used to teardown Hyper-V clockevent devices on module unload. Signed-off-by: Vitaly Kuznetsov Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman diff --git a/kernel/time/clockevents.c b/kernel/time/clockevents.c index 5544990..888ecc1 100644 --- a/kernel/time/clockevents.c +++ b/kernel/time/clockevents.c @@ -371,7 +371,7 @@ int clockevents_unbind_device(struct clock_event_device *ced, int cpu) mutex_unlock(&clockevents_mutex); return ret; } -EXPORT_SYMBOL_GPL(clockevents_unbind); +EXPORT_SYMBOL_GPL(clockevents_unbind_device); /** * clockevents_register_device - register a clock event device -- cgit v0.10.2 From e086748c655ab99bac91b87d1bb59d9cc45867b9 Mon Sep 17 00:00:00 2001 From: Vitaly Kuznetsov Date: Fri, 27 Feb 2015 11:25:57 -0800 Subject: Drivers: hv: vmbus: Teardown clockevent devices on module unload Newly introduced clockevent devices made it impossible to unload hv_vmbus module as clockevents_config_and_register() takes additional reverence to the module. To make it possible again we do the following: - avoid setting dev->owner for clockevent devices; - implement hv_synic_clockevents_cleanup() doing clockevents_unbind_device(); - call it from vmbus_exit(). In theory hv_synic_clockevents_cleanup() can be merged with hv_synic_cleanup(), however, we call hv_synic_cleanup() from smp_call_function_single() and this doesn't work for clockevents_unbind_device() as it does such call on its own. I opted for a separate function. Signed-off-by: Vitaly Kuznetsov Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/hv/hv.c b/drivers/hv/hv.c index 39531dc..d3943bc 100644 --- a/drivers/hv/hv.c +++ b/drivers/hv/hv.c @@ -312,7 +312,11 @@ static void hv_init_clockevent_device(struct clock_event_device *dev, int cpu) dev->features = CLOCK_EVT_FEAT_ONESHOT; dev->cpumask = cpumask_of(cpu); dev->rating = 1000; - dev->owner = THIS_MODULE; + /* + * Avoid settint dev->owner = THIS_MODULE deliberately as doing so will + * result in clockevents_config_and_register() taking additional + * references to the hv_vmbus module making it impossible to unload. + */ dev->set_mode = hv_ce_setmode; dev->set_next_event = hv_ce_set_next_event; @@ -470,6 +474,20 @@ void hv_synic_init(void *arg) } /* + * hv_synic_clockevents_cleanup - Cleanup clockevent devices + */ +void hv_synic_clockevents_cleanup(void) +{ + int cpu; + + if (!(ms_hyperv.features & HV_X64_MSR_SYNTIMER_AVAILABLE)) + return; + + for_each_online_cpu(cpu) + clockevents_unbind_device(hv_context.clk_evt[cpu], cpu); +} + +/* * hv_synic_cleanup - Cleanup routine for hv_synic_init(). */ void hv_synic_cleanup(void *arg) @@ -483,6 +501,11 @@ void hv_synic_cleanup(void *arg) if (!hv_context.synic_initialized) return; + /* Turn off clockevent device */ + if (ms_hyperv.features & HV_X64_MSR_SYNTIMER_AVAILABLE) + hv_ce_setmode(CLOCK_EVT_MODE_SHUTDOWN, + hv_context.clk_evt[cpu]); + rdmsrl(HV_X64_MSR_SINT0 + VMBUS_MESSAGE_SINT, shared_sint.as_uint64); shared_sint.masked = 1; diff --git a/drivers/hv/hyperv_vmbus.h b/drivers/hv/hyperv_vmbus.h index 6cf2de9..b055e53 100644 --- a/drivers/hv/hyperv_vmbus.h +++ b/drivers/hv/hyperv_vmbus.h @@ -572,6 +572,8 @@ extern void hv_synic_init(void *irqarg); extern void hv_synic_cleanup(void *arg); +extern void hv_synic_clockevents_cleanup(void); + /* * Host version information. */ diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c index d8233d4..6d99aa5 100644 --- a/drivers/hv/vmbus_drv.c +++ b/drivers/hv/vmbus_drv.c @@ -1032,6 +1032,7 @@ static void __exit vmbus_exit(void) int cpu; vmbus_connection.conn_state = DISCONNECTED; + hv_synic_clockevents_cleanup(); hv_remove_vmbus_irq(); vmbus_free_channels(); bus_unregister(&hv_bus); -- cgit v0.10.2 From 1896566372579939eb86414ea3d664f18d5e909c Mon Sep 17 00:00:00 2001 From: Dexuan Cui Date: Fri, 27 Feb 2015 11:25:58 -0800 Subject: hv: hv_util: move vmbus_open() to a later place Before the line vmbus_open() returns, srv->util_cb can be already running and the variables, like util_fw_version, are needed by the srv->util_cb. So we have to make sure the variables are initialized before the vmbus_open(). CC: "K. Y. Srinivasan" Reviewed-by: Vitaly Kuznetsov Reviewed-by: Jason Wang Signed-off-by: Dexuan Cui Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/hv/hv_util.c b/drivers/hv/hv_util.c index 3b9c9ef..c5be773 100644 --- a/drivers/hv/hv_util.c +++ b/drivers/hv/hv_util.c @@ -340,12 +340,8 @@ static int util_probe(struct hv_device *dev, set_channel_read_state(dev->channel, false); - ret = vmbus_open(dev->channel, 4 * PAGE_SIZE, 4 * PAGE_SIZE, NULL, 0, - srv->util_cb, dev->channel); - if (ret) - goto error; - hv_set_drvdata(dev, srv); + /* * Based on the host; initialize the framework and * service version numbers we will negotiate. @@ -365,6 +361,11 @@ static int util_probe(struct hv_device *dev, hb_srv_version = HB_VERSION; } + ret = vmbus_open(dev->channel, 4 * PAGE_SIZE, 4 * PAGE_SIZE, NULL, 0, + srv->util_cb, dev->channel); + if (ret) + goto error; + return 0; error: -- cgit v0.10.2 From 89f9f6796d41e10e224b0cb0027ddd78cb881f65 Mon Sep 17 00:00:00 2001 From: Dexuan Cui Date: Fri, 27 Feb 2015 11:25:59 -0800 Subject: hv: vmbus_post_msg: retry the hypercall on some transient errors I got HV_STATUS_INVALID_CONNECTION_ID on Hyper-V 2008 R2 when keeping running "rmmod hv_netvsc; modprobe hv_netvsc; rmmod hv_utils; modprobe hv_utils" in a Linux guest. Looks the host has some kind of throttling mechanism if some kinds of hypercalls are sent too frequently. Without the patch, the driver can occasionally fail to load. Also let's retry HV_STATUS_INSUFFICIENT_MEMORY, though we didn't get it before. Removed 'case -ENOMEM', since the hypervisor doesn't return this. CC: "K. Y. Srinivasan" Reviewed-by: Jason Wang Signed-off-by: Dexuan Cui Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman diff --git a/arch/x86/include/uapi/asm/hyperv.h b/arch/x86/include/uapi/asm/hyperv.h index 90c458e..ce6068d 100644 --- a/arch/x86/include/uapi/asm/hyperv.h +++ b/arch/x86/include/uapi/asm/hyperv.h @@ -225,6 +225,8 @@ #define HV_STATUS_INVALID_HYPERCALL_CODE 2 #define HV_STATUS_INVALID_HYPERCALL_INPUT 3 #define HV_STATUS_INVALID_ALIGNMENT 4 +#define HV_STATUS_INSUFFICIENT_MEMORY 11 +#define HV_STATUS_INVALID_CONNECTION_ID 18 #define HV_STATUS_INSUFFICIENT_BUFFERS 19 typedef struct _HV_REFERENCE_TSC_PAGE { diff --git a/drivers/hv/connection.c b/drivers/hv/connection.c index c4acd1c..af2388f 100644 --- a/drivers/hv/connection.c +++ b/drivers/hv/connection.c @@ -440,9 +440,16 @@ int vmbus_post_msg(void *buffer, size_t buflen) ret = hv_post_message(conn_id, 1, buffer, buflen); switch (ret) { + case HV_STATUS_INVALID_CONNECTION_ID: + /* + * We could get this if we send messages too + * frequently. + */ + ret = -EAGAIN; + break; + case HV_STATUS_INSUFFICIENT_MEMORY: case HV_STATUS_INSUFFICIENT_BUFFERS: ret = -ENOMEM; - case -ENOMEM: break; case HV_STATUS_SUCCESS: return ret; @@ -452,7 +459,7 @@ int vmbus_post_msg(void *buffer, size_t buflen) } retries++; - msleep(100); + msleep(1000); } return ret; } -- cgit v0.10.2 From ac0d12b7cee73b4b4b769ea58c62ec7042c6be13 Mon Sep 17 00:00:00 2001 From: Dexuan Cui Date: Fri, 27 Feb 2015 11:26:00 -0800 Subject: hv: vmbus_open(): reset the channel state on ENOMEM Without this patch, the state is put to CHANNEL_OPENING_STATE, and when the driver is loaded next time, vmbus_open() will fail immediately due to newchannel->state != CHANNEL_OPEN_STATE. CC: "K. Y. Srinivasan" Signed-off-by: Dexuan Cui Reviewed-by: Jason Wang Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/hv/channel.c b/drivers/hv/channel.c index 2978f5e..26dcf26 100644 --- a/drivers/hv/channel.c +++ b/drivers/hv/channel.c @@ -89,9 +89,10 @@ int vmbus_open(struct vmbus_channel *newchannel, u32 send_ringbuffer_size, out = (void *)__get_free_pages(GFP_KERNEL|__GFP_ZERO, get_order(send_ringbuffer_size + recv_ringbuffer_size)); - if (!out) - return -ENOMEM; - + if (!out) { + err = -ENOMEM; + goto error0; + } in = (void *)((unsigned long)out + send_ringbuffer_size); @@ -199,6 +200,7 @@ error0: free_pages((unsigned long)out, get_order(send_ringbuffer_size + recv_ringbuffer_size)); kfree(open_info); + newchannel->state = CHANNEL_OPEN_STATE; return err; } EXPORT_SYMBOL_GPL(vmbus_open); -- cgit v0.10.2 From 08a9513f7417d6dff9a1ab0c163900f503ff84eb Mon Sep 17 00:00:00 2001 From: Nicholas Mc Guire Date: Fri, 27 Feb 2015 11:26:01 -0800 Subject: hv: channel: match var type to return type of wait_for_completion return type of wait_for_completion_timeout is unsigned long not int, this patch changes the type of t from int to unsigned long. Signed-off-by: Nicholas Mc Guire Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/hv/channel.c b/drivers/hv/channel.c index 26dcf26..f687995 100644 --- a/drivers/hv/channel.c +++ b/drivers/hv/channel.c @@ -71,7 +71,8 @@ int vmbus_open(struct vmbus_channel *newchannel, u32 send_ringbuffer_size, struct vmbus_channel_msginfo *open_info = NULL; void *in, *out; unsigned long flags; - int ret, t, err = 0; + int ret, err = 0; + unsigned long t; spin_lock_irqsave(&newchannel->lock, flags); if (newchannel->state == CHANNEL_OPEN_STATE) { -- cgit v0.10.2 From 51e5181d20086c0b26e871deffeff3ff47cf5c4b Mon Sep 17 00:00:00 2001 From: Nicholas Mc Guire Date: Fri, 27 Feb 2015 11:26:02 -0800 Subject: hv: channel_mgmt: match var type to return type of wait_for_completion return type of wait_for_completion_timeout is unsigned long not int, this patch changes the type of t from int to unsigned long. Signed-off-by: Nicholas Mc Guire Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/hv/channel_mgmt.c b/drivers/hv/channel_mgmt.c index 36bacc7..6be93f0 100644 --- a/drivers/hv/channel_mgmt.c +++ b/drivers/hv/channel_mgmt.c @@ -789,7 +789,8 @@ int vmbus_request_offers(void) { struct vmbus_channel_message_header *msg; struct vmbus_channel_msginfo *msginfo; - int ret, t; + int ret; + unsigned long t; msginfo = kmalloc(sizeof(*msginfo) + sizeof(struct vmbus_channel_message_header), -- cgit v0.10.2 From b057b3ad16e1c6e21e234cd7f5c61c525da97f17 Mon Sep 17 00:00:00 2001 From: Nicholas Mc Guire Date: Fri, 27 Feb 2015 11:26:03 -0800 Subject: hv: hv_balloon: match var type to return type of wait_for_completion return type of wait_for_completion_timeout is unsigned long not int, this patch changes the type of t from int to unsigned long. Signed-off-by: Nicholas Mc Guire Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/hv/hv_balloon.c b/drivers/hv/hv_balloon.c index ff16938..965c37a 100644 --- a/drivers/hv/hv_balloon.c +++ b/drivers/hv/hv_balloon.c @@ -1414,7 +1414,8 @@ static void balloon_onchannelcallback(void *context) static int balloon_probe(struct hv_device *dev, const struct hv_vmbus_device_id *dev_id) { - int ret, t; + int ret; + unsigned long t; struct dm_version_request version_req; struct dm_capabilities cap_msg; -- cgit v0.10.2 From 40384e4bbeb9f2651fe9bffc0062d9f31ef625bf Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Fri, 27 Feb 2015 11:26:04 -0800 Subject: Drivers: hv: vmbus: Fix a bug in the error path in vmbus_open() Correctly rollback state if the failure occurs after we have handed over the ownership of the buffer to the host. Signed-off-by: K. Y. Srinivasan Cc: stable@vger.kernel.org Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/hv/channel.c b/drivers/hv/channel.c index f687995..bf0cf8f 100644 --- a/drivers/hv/channel.c +++ b/drivers/hv/channel.c @@ -137,7 +137,7 @@ int vmbus_open(struct vmbus_channel *newchannel, u32 send_ringbuffer_size, GFP_KERNEL); if (!open_info) { err = -ENOMEM; - goto error0; + goto error_gpadl; } init_completion(&open_info->waitevent); @@ -153,7 +153,7 @@ int vmbus_open(struct vmbus_channel *newchannel, u32 send_ringbuffer_size, if (userdatalen > MAX_USER_DEFINED_BYTES) { err = -EINVAL; - goto error0; + goto error_gpadl; } if (userdatalen) @@ -197,6 +197,9 @@ error1: list_del(&open_info->msglistentry); spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock, flags); +error_gpadl: + vmbus_teardown_gpadl(newchannel, newchannel->ringbuffer_gpadlhandle); + error0: free_pages((unsigned long)out, get_order(send_ringbuffer_size + recv_ringbuffer_size)); -- cgit v0.10.2 From 04653a009a63d6a91c60a5449ea97e3ed5e1dc29 Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Fri, 27 Feb 2015 11:26:05 -0800 Subject: Drivers: hv: vmbus: Add support for the NetworkDirect GUID NetworkDirect is a service that supports guest RDMA. Define the GUID for this service. Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/hv/channel_mgmt.c b/drivers/hv/channel_mgmt.c index 6be93f0..0ba6b5c 100644 --- a/drivers/hv/channel_mgmt.c +++ b/drivers/hv/channel_mgmt.c @@ -413,6 +413,8 @@ static const struct hv_vmbus_device_id hp_devs[] = { { HV_SCSI_GUID, }, /* Network */ { HV_NIC_GUID, }, + /* NetworkDirect Guest RDMA */ + { HV_ND_GUID, }, }; diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index 26a32b7..7d976ac 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -1110,6 +1110,16 @@ void vmbus_driver_unregister(struct hv_driver *hv_driver); } /* + * NetworkDirect. This is the guest RDMA service. + * {8c2eaf3d-32a7-4b09-ab99-bd1f1c86b501} + */ +#define HV_ND_GUID \ + .guid = { \ + 0x3d, 0xaf, 0x2e, 0x8c, 0xa7, 0x32, 0x09, 0x4b, \ + 0xab, 0x99, 0xbd, 0x1f, 0x1c, 0x86, 0xb5, 0x01 \ + } + +/* * Common header for Hyper-V ICs */ -- cgit v0.10.2 From d15a0301c4157884d1a48a5d76b9ac3e36d71242 Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Sat, 28 Feb 2015 11:18:16 -0800 Subject: Drivers: hv: vmbus: Properly handle child device remove Handle the case when the device may be removed when the device has no driver attached to it. Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c index 6d99aa5..a12666d 100644 --- a/drivers/hv/vmbus_drv.c +++ b/drivers/hv/vmbus_drv.c @@ -508,14 +508,17 @@ static int vmbus_probe(struct device *child_device) */ static int vmbus_remove(struct device *child_device) { - struct hv_driver *drv = drv_to_hv_drv(child_device->driver); + struct hv_driver *drv; struct hv_device *dev = device_to_hv_device(child_device); - if (drv->remove) - drv->remove(dev); - else - pr_err("remove not set for driver %s\n", - dev_name(child_device)); + if (child_device->driver) { + drv = drv_to_hv_drv(child_device->driver); + if (drv->remove) + drv->remove(dev); + else + pr_err("remove not set for driver %s\n", + dev_name(child_device)); + } return 0; } -- cgit v0.10.2 From ed6cfcc5fdf2ebca320b6f74c836e555e18216e1 Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Sat, 28 Feb 2015 11:18:17 -0800 Subject: Drivers: hv: vmbus: Introduce a function to remove a rescinded offer In response to a rescind message, we need to remove the channel and the corresponding device. Cleanup this code path by factoring out the code to remove a channel. Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/hv/channel.c b/drivers/hv/channel.c index bf0cf8f..9b79aca 100644 --- a/drivers/hv/channel.c +++ b/drivers/hv/channel.c @@ -501,6 +501,15 @@ static int vmbus_close_internal(struct vmbus_channel *channel) put_cpu(); } + /* + * If the channel has been rescinded; process device removal. + */ + if (channel->rescind) { + hv_process_channel_removal(channel, + channel->offermsg.child_relid); + return 0; + } + /* Send a closing message */ msg = &channel->close_msg.msg; diff --git a/drivers/hv/channel_mgmt.c b/drivers/hv/channel_mgmt.c index 0ba6b5c..b933891 100644 --- a/drivers/hv/channel_mgmt.c +++ b/drivers/hv/channel_mgmt.c @@ -207,33 +207,21 @@ static void percpu_channel_deq(void *arg) list_del(&channel->percpu_list); } -/* - * vmbus_process_rescind_offer - - * Rescind the offer by initiating a device removal - */ -static void vmbus_process_rescind_offer(struct work_struct *work) + +void hv_process_channel_removal(struct vmbus_channel *channel, u32 relid) { - struct vmbus_channel *channel = container_of(work, - struct vmbus_channel, - work); + struct vmbus_channel_relid_released msg; unsigned long flags; struct vmbus_channel *primary_channel; - struct vmbus_channel_relid_released msg; - struct device *dev; - - if (channel->device_obj) { - dev = get_device(&channel->device_obj->device); - if (dev) { - vmbus_device_unregister(channel->device_obj); - put_device(dev); - } - } memset(&msg, 0, sizeof(struct vmbus_channel_relid_released)); - msg.child_relid = channel->offermsg.child_relid; + msg.child_relid = relid; msg.header.msgtype = CHANNELMSG_RELID_RELEASED; vmbus_post_msg(&msg, sizeof(struct vmbus_channel_relid_released)); + if (channel == NULL) + return; + if (channel->target_cpu != get_cpu()) { put_cpu(); smp_call_function_single(channel->target_cpu, @@ -256,6 +244,29 @@ static void vmbus_process_rescind_offer(struct work_struct *work) free_channel(channel); } +/* + * vmbus_process_rescind_offer - + * Rescind the offer by initiating a device removal + */ +static void vmbus_process_rescind_offer(struct work_struct *work) +{ + struct vmbus_channel *channel = container_of(work, + struct vmbus_channel, + work); + struct device *dev; + + if (channel->device_obj) { + dev = get_device(&channel->device_obj->device); + if (dev) { + vmbus_device_unregister(channel->device_obj); + put_device(dev); + } + } else { + hv_process_channel_removal(channel, + channel->offermsg.child_relid); + } +} + void vmbus_free_channels(void) { struct vmbus_channel *channel; diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c index a12666d..2b7b51d 100644 --- a/drivers/hv/vmbus_drv.c +++ b/drivers/hv/vmbus_drv.c @@ -510,14 +510,23 @@ static int vmbus_remove(struct device *child_device) { struct hv_driver *drv; struct hv_device *dev = device_to_hv_device(child_device); + u32 relid = dev->channel->offermsg.child_relid; if (child_device->driver) { drv = drv_to_hv_drv(child_device->driver); if (drv->remove) drv->remove(dev); - else + else { + hv_process_channel_removal(dev->channel, relid); pr_err("remove not set for driver %s\n", dev_name(child_device)); + } + } else { + /* + * We don't have a driver for this device; deal with the + * rescind message by removing the channel. + */ + hv_process_channel_removal(dev->channel, relid); } return 0; diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index 7d976ac..dd92a85 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -1226,6 +1226,7 @@ void hv_kvp_onchannelcallback(void *); int hv_vss_init(struct hv_util_service *); void hv_vss_deinit(void); void hv_vss_onchannelcallback(void *); +void hv_process_channel_removal(struct vmbus_channel *channel, u32 relid); extern struct resource hyperv_mmio; -- cgit v0.10.2 From 2dd37cb81580dce6dfb8c5a7d5c37b904a188ae7 Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Sat, 28 Feb 2015 11:18:18 -0800 Subject: Drivers: hv: vmbus: Handle both rescind and offer messages in the same context Execute both ressind and offer messages in the same work context. This serializes these operations and naturally addresses the various corner cases. Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/hv/channel_mgmt.c b/drivers/hv/channel_mgmt.c index b933891..f8528e1 100644 --- a/drivers/hv/channel_mgmt.c +++ b/drivers/hv/channel_mgmt.c @@ -134,6 +134,34 @@ fw_error: EXPORT_SYMBOL_GPL(vmbus_prep_negotiate_resp); +static void vmbus_process_device_unregister(struct work_struct *work) +{ + struct device *dev; + struct vmbus_channel *channel = container_of(work, + struct vmbus_channel, + work); + + dev = get_device(&channel->device_obj->device); + if (dev) { + vmbus_device_unregister(channel->device_obj); + put_device(dev); + } +} + +static void vmbus_sc_creation_cb(struct work_struct *work) +{ + struct vmbus_channel *newchannel = container_of(work, + struct vmbus_channel, + work); + struct vmbus_channel *primary_channel = newchannel->primary_channel; + + /* + * On entry sc_creation_callback has been already verified to + * be non-NULL. + */ + primary_channel->sc_creation_callback(newchannel); +} + /* * alloc_channel - Allocate and initialize a vmbus channel object */ @@ -244,29 +272,6 @@ void hv_process_channel_removal(struct vmbus_channel *channel, u32 relid) free_channel(channel); } -/* - * vmbus_process_rescind_offer - - * Rescind the offer by initiating a device removal - */ -static void vmbus_process_rescind_offer(struct work_struct *work) -{ - struct vmbus_channel *channel = container_of(work, - struct vmbus_channel, - work); - struct device *dev; - - if (channel->device_obj) { - dev = get_device(&channel->device_obj->device); - if (dev) { - vmbus_device_unregister(channel->device_obj); - put_device(dev); - } - } else { - hv_process_channel_removal(channel, - channel->offermsg.child_relid); - } -} - void vmbus_free_channels(void) { struct vmbus_channel *channel; @@ -281,11 +286,8 @@ void vmbus_free_channels(void) * vmbus_process_offer - Process the offer by creating a channel/device * associated with this offer */ -static void vmbus_process_offer(struct work_struct *work) +static void vmbus_process_offer(struct vmbus_channel *newchannel) { - struct vmbus_channel *newchannel = container_of(work, - struct vmbus_channel, - work); struct vmbus_channel *channel; bool fnew = true; bool enq = false; @@ -349,9 +351,19 @@ static void vmbus_process_offer(struct work_struct *work) newchannel->state = CHANNEL_OPEN_STATE; if (channel->sc_creation_callback != NULL) - channel->sc_creation_callback(newchannel); - - goto done_init_rescind; + /* + * We need to invoke the sub-channel creation + * callback; invoke this in a seperate work + * context since we are currently running on + * the global work context in which we handle + * messages from the host. + */ + INIT_WORK(&newchannel->work, + vmbus_sc_creation_cb); + queue_work(newchannel->controlwq, + &newchannel->work); + + return; } goto err_free_chan; @@ -392,15 +404,9 @@ static void vmbus_process_offer(struct work_struct *work) kfree(newchannel->device_obj); goto err_free_chan; } -done_init_rescind: - spin_lock_irqsave(&newchannel->lock, flags); - /* The next possible work is rescind handling */ - INIT_WORK(&newchannel->work, vmbus_process_rescind_offer); - /* Check if rescind offer was already received */ - if (newchannel->rescind) - queue_work(newchannel->controlwq, &newchannel->work); - spin_unlock_irqrestore(&newchannel->lock, flags); + return; + err_free_chan: free_channel(newchannel); } @@ -526,8 +532,7 @@ static void vmbus_onoffer(struct vmbus_channel_message_header *hdr) newchannel->monitor_grp = (u8)offer->monitorid / 32; newchannel->monitor_bit = (u8)offer->monitorid % 32; - INIT_WORK(&newchannel->work, vmbus_process_offer); - queue_work(newchannel->controlwq, &newchannel->work); + vmbus_process_offer(newchannel); } /* @@ -544,24 +549,28 @@ static void vmbus_onoffer_rescind(struct vmbus_channel_message_header *hdr) rescind = (struct vmbus_channel_rescind_offer *)hdr; channel = relid2channel(rescind->child_relid); - if (channel == NULL) - /* Just return here, no channel found */ + if (channel == NULL) { + hv_process_channel_removal(NULL, rescind->child_relid); return; + } spin_lock_irqsave(&channel->lock, flags); channel->rescind = true; - /* - * channel->work.func != vmbus_process_rescind_offer means we are still - * processing offer request and the rescind offer processing should be - * postponed. It will be done at the very end of vmbus_process_offer() - * as rescind flag is being checked there. - */ - if (channel->work.func == vmbus_process_rescind_offer) - /* work is initialized for vmbus_process_rescind_offer() from - * vmbus_process_offer() where the channel got created */ - queue_work(channel->controlwq, &channel->work); - spin_unlock_irqrestore(&channel->lock, flags); + + if (channel->device_obj) { + /* + * We will have to unregister this device from the + * driver core. Do this in the per-channel work context. + * Note that we are currently executing on the global + * workq for handling messages from the host. + */ + INIT_WORK(&channel->work, vmbus_process_device_unregister); + queue_work(channel->controlwq, &channel->work); + } else { + hv_process_channel_removal(channel, + channel->offermsg.child_relid); + } } /* -- cgit v0.10.2 From 5b1e5b530753e660080d402b81b88684893157d5 Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Sat, 28 Feb 2015 11:18:19 -0800 Subject: Drivers: hv: vmbus: Remove the channel from the channel list(s) on failure Properly rollback state in vmbus_pocess_offer() in the failure paths. Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/hv/channel_mgmt.c b/drivers/hv/channel_mgmt.c index f8528e1..b1e5a5f 100644 --- a/drivers/hv/channel_mgmt.c +++ b/drivers/hv/channel_mgmt.c @@ -386,7 +386,7 @@ static void vmbus_process_offer(struct vmbus_channel *newchannel) &newchannel->offermsg.offer.if_instance, newchannel); if (!newchannel->device_obj) - goto err_free_chan; + goto err_deq_chan; /* * Add the new device to the bus. This will kick off device-driver @@ -398,15 +398,26 @@ static void vmbus_process_offer(struct vmbus_channel *newchannel) pr_err("unable to add child device object (relid %d)\n", newchannel->offermsg.child_relid); - spin_lock_irqsave(&vmbus_connection.channel_lock, flags); - list_del(&newchannel->listentry); - spin_unlock_irqrestore(&vmbus_connection.channel_lock, flags); kfree(newchannel->device_obj); - goto err_free_chan; + goto err_deq_chan; } return; +err_deq_chan: + spin_lock_irqsave(&vmbus_connection.channel_lock, flags); + list_del(&newchannel->listentry); + spin_unlock_irqrestore(&vmbus_connection.channel_lock, flags); + + if (newchannel->target_cpu != get_cpu()) { + put_cpu(); + smp_call_function_single(newchannel->target_cpu, + percpu_channel_deq, newchannel, true); + } else { + percpu_channel_deq(newchannel); + put_cpu(); + } + err_free_chan: free_channel(newchannel); } -- cgit v0.10.2 From 5380b383ba10915e61e3db09341cf8193f0601ad Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Sat, 28 Feb 2015 11:18:20 -0800 Subject: Drivers: hv: util: On device remove, close the channel after de-initializing the service When the offer is rescinded, vmbus_close() can free up the channel; deinitialize the service before closing the channel. Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/hv/hv_util.c b/drivers/hv/hv_util.c index c5be773..7994ec2 100644 --- a/drivers/hv/hv_util.c +++ b/drivers/hv/hv_util.c @@ -380,9 +380,9 @@ static int util_remove(struct hv_device *dev) { struct hv_util_service *srv = hv_get_drvdata(dev); - vmbus_close(dev->channel); if (srv->util_deinit) srv->util_deinit(); + vmbus_close(dev->channel); kfree(srv->recv_buffer); return 0; -- cgit v0.10.2 From 37f492ce8148d84261cdb9fe1e925199f47531ae Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Sat, 28 Feb 2015 11:18:21 -0800 Subject: Drivers: hv: vmbus: Get rid of some unnecessary messages Currently we log messages when either we are not able to map an ID to a channel or when the channel does not have a callback associated (in the channel interrupt handling path). These messages don't add any value, get rid of them. Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/hv/connection.c b/drivers/hv/connection.c index af2388f..583d7d4 100644 --- a/drivers/hv/connection.c +++ b/drivers/hv/connection.c @@ -318,10 +318,8 @@ static void process_chn_event(u32 relid) */ channel = pcpu_relid2channel(relid); - if (!channel) { - pr_err("channel not found for relid - %u\n", relid); + if (!channel) return; - } /* * A channel once created is persistent even when there @@ -356,10 +354,7 @@ static void process_chn_event(u32 relid) else bytes_to_read = 0; } while (read_state && (bytes_to_read != 0)); - } else { - pr_err("no channel callback for relid - %u\n", relid); } - } /* -- cgit v0.10.2 From b05d8d9ef5ef21d1b18440430f950304836e1aaa Mon Sep 17 00:00:00 2001 From: Vitaly Kuznetsov Date: Sat, 28 Feb 2015 11:38:58 -0800 Subject: Drivers: hv: hv_balloon: eliminate the trylock path in acquire/release_region_mutex When many memory regions are being added and automatically onlined the following lockup is sometimes observed: INFO: task udevd:1872 blocked for more than 120 seconds. ... Call Trace: [] schedule_timeout+0x22c/0x350 [] wait_for_common+0x10f/0x160 [] ? default_wake_function+0x0/0x20 [] wait_for_completion+0x1d/0x20 [] hv_memory_notifier+0xdc/0x120 [] notifier_call_chain+0x4c/0x70 ... When several memory blocks are going online simultaneously we got several hv_memory_notifier() trying to acquire the ha_region_mutex. When this mutex is being held by hot_add_req() all these competing acquire_region_mutex() do mutex_trylock, fail, and queue themselves into wait_for_completion(..). However when we do complete() from release_region_mutex() only one of them wakes up. This could be solved by changing complete() -> complete_all() memory onlining can be delayed as well, in that case we can still get several hv_memory_notifier() runners at the same time trying to grab the mutex. Only one of them will succeed and the others will hang for forever as complete() is not being called. We don't see this issue often because we have 5sec onlining timeout in hv_mem_hot_add() and usually all udev events arrive in this time frame. Get rid of the trylock path, waiting on the mutex is supposed to provide the required serialization. Signed-off-by: Vitaly Kuznetsov Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/hv/hv_balloon.c b/drivers/hv/hv_balloon.c index 965c37a..ea3d33c 100644 --- a/drivers/hv/hv_balloon.c +++ b/drivers/hv/hv_balloon.c @@ -534,7 +534,6 @@ struct hv_dynmem_device { struct task_struct *thread; struct mutex ha_region_mutex; - struct completion waiter_event; /* * A list of hot-add regions. @@ -554,38 +553,17 @@ static struct hv_dynmem_device dm_device; static void post_status(struct hv_dynmem_device *dm); #ifdef CONFIG_MEMORY_HOTPLUG -static void acquire_region_mutex(bool trylock) -{ - if (trylock) { - reinit_completion(&dm_device.waiter_event); - while (!mutex_trylock(&dm_device.ha_region_mutex)) - wait_for_completion(&dm_device.waiter_event); - } else { - mutex_lock(&dm_device.ha_region_mutex); - } -} - -static void release_region_mutex(bool trylock) -{ - if (trylock) { - mutex_unlock(&dm_device.ha_region_mutex); - } else { - mutex_unlock(&dm_device.ha_region_mutex); - complete(&dm_device.waiter_event); - } -} - static int hv_memory_notifier(struct notifier_block *nb, unsigned long val, void *v) { switch (val) { case MEM_GOING_ONLINE: - acquire_region_mutex(true); + mutex_lock(&dm_device.ha_region_mutex); break; case MEM_ONLINE: case MEM_CANCEL_ONLINE: - release_region_mutex(true); + mutex_unlock(&dm_device.ha_region_mutex); if (dm_device.ha_waiting) { dm_device.ha_waiting = false; complete(&dm_device.ol_waitevent); @@ -646,7 +624,7 @@ static void hv_mem_hot_add(unsigned long start, unsigned long size, init_completion(&dm_device.ol_waitevent); dm_device.ha_waiting = true; - release_region_mutex(false); + mutex_unlock(&dm_device.ha_region_mutex); nid = memory_add_physaddr_to_nid(PFN_PHYS(start_pfn)); ret = add_memory(nid, PFN_PHYS((start_pfn)), (HA_CHUNK << PAGE_SHIFT)); @@ -675,7 +653,7 @@ static void hv_mem_hot_add(unsigned long start, unsigned long size, * have not been "onlined" within the allowed time. */ wait_for_completion_timeout(&dm_device.ol_waitevent, 5*HZ); - acquire_region_mutex(false); + mutex_lock(&dm_device.ha_region_mutex); post_status(&dm_device); } @@ -886,7 +864,7 @@ static void hot_add_req(struct work_struct *dummy) resp.hdr.size = sizeof(struct dm_hot_add_response); #ifdef CONFIG_MEMORY_HOTPLUG - acquire_region_mutex(false); + mutex_lock(&dm_device.ha_region_mutex); pg_start = dm->ha_wrk.ha_page_range.finfo.start_page; pfn_cnt = dm->ha_wrk.ha_page_range.finfo.page_cnt; @@ -918,7 +896,7 @@ static void hot_add_req(struct work_struct *dummy) if (do_hot_add) resp.page_count = process_hot_add(pg_start, pfn_cnt, rg_start, rg_sz); - release_region_mutex(false); + mutex_unlock(&dm_device.ha_region_mutex); #endif /* * The result field of the response structure has the @@ -1440,7 +1418,6 @@ static int balloon_probe(struct hv_device *dev, dm_device.next_version = DYNMEM_PROTOCOL_VERSION_WIN7; init_completion(&dm_device.host_event); init_completion(&dm_device.config_event); - init_completion(&dm_device.waiter_event); INIT_LIST_HEAD(&dm_device.ha_region_list); mutex_init(&dm_device.ha_region_mutex); INIT_WORK(&dm_device.balloon_wrk.wrk, balloon_up); -- cgit v0.10.2 From 549fd280b145e2154db5a7d06981d041f8bbf597 Mon Sep 17 00:00:00 2001 From: Vitaly Kuznetsov Date: Sat, 28 Feb 2015 11:38:59 -0800 Subject: Drivers: hv: hv_balloon: report offline pages as being used When hot-added memory pages are not brought online or when some memory blocks are sent offline the subsequent ballooning process kills the guest with OOM killer. This happens as we don't report these pages as neither used nor free and apparently host algorithm considers them as being unused. Keep track of all online/offline operations and report all currently offline pages as being used so host won't try to balloon them out. Signed-off-by: Vitaly Kuznetsov Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/hv/hv_balloon.c b/drivers/hv/hv_balloon.c index ea3d33c..53932b3 100644 --- a/drivers/hv/hv_balloon.c +++ b/drivers/hv/hv_balloon.c @@ -503,6 +503,8 @@ struct hv_dynmem_device { * Number of pages we have currently ballooned out. */ unsigned int num_pages_ballooned; + unsigned int num_pages_onlined; + unsigned int num_pages_added; /* * State to manage the ballooning (up) operation. @@ -556,12 +558,15 @@ static void post_status(struct hv_dynmem_device *dm); static int hv_memory_notifier(struct notifier_block *nb, unsigned long val, void *v) { + struct memory_notify *mem = (struct memory_notify *)v; + switch (val) { case MEM_GOING_ONLINE: mutex_lock(&dm_device.ha_region_mutex); break; case MEM_ONLINE: + dm_device.num_pages_onlined += mem->nr_pages; case MEM_CANCEL_ONLINE: mutex_unlock(&dm_device.ha_region_mutex); if (dm_device.ha_waiting) { @@ -570,8 +575,12 @@ static int hv_memory_notifier(struct notifier_block *nb, unsigned long val, } break; - case MEM_GOING_OFFLINE: case MEM_OFFLINE: + mutex_lock(&dm_device.ha_region_mutex); + dm_device.num_pages_onlined -= mem->nr_pages; + mutex_unlock(&dm_device.ha_region_mutex); + break; + case MEM_GOING_OFFLINE: case MEM_CANCEL_OFFLINE: break; } @@ -896,6 +905,8 @@ static void hot_add_req(struct work_struct *dummy) if (do_hot_add) resp.page_count = process_hot_add(pg_start, pfn_cnt, rg_start, rg_sz); + + dm->num_pages_added += resp.page_count; mutex_unlock(&dm_device.ha_region_mutex); #endif /* @@ -1009,17 +1020,21 @@ static void post_status(struct hv_dynmem_device *dm) status.hdr.trans_id = atomic_inc_return(&trans_id); /* - * The host expects the guest to report free memory. - * Further, the host expects the pressure information to - * include the ballooned out pages. - * For a given amount of memory that we are managing, we - * need to compute a floor below which we should not balloon. - * Compute this and add it to the pressure report. + * The host expects the guest to report free and committed memory. + * Furthermore, the host expects the pressure information to include + * the ballooned out pages. For a given amount of memory that we are + * managing we need to compute a floor below which we should not + * balloon. Compute this and add it to the pressure report. + * We also need to report all offline pages (num_pages_added - + * num_pages_onlined) as committed to the host, otherwise it can try + * asking us to balloon them out. */ status.num_avail = val.freeram; status.num_committed = vm_memory_committed() + - dm->num_pages_ballooned + - compute_balloon_floor(); + dm->num_pages_ballooned + + (dm->num_pages_added > dm->num_pages_onlined ? + dm->num_pages_added - dm->num_pages_onlined : 0) + + compute_balloon_floor(); /* * If our transaction ID is no longer current, just don't -- cgit v0.10.2 From 530d15b907038735d4cb008dc5caae7ce4dfc985 Mon Sep 17 00:00:00 2001 From: Vitaly Kuznetsov Date: Sat, 28 Feb 2015 11:39:00 -0800 Subject: Drivers: hv: hv_balloon: refuse to balloon below the floor When host asks us to balloon up we need to be sure we're not committing suicide by overballooning. Use already existent 'floor' metric as our lowest possible value for free ram. Signed-off-by: Vitaly Kuznetsov Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/hv/hv_balloon.c b/drivers/hv/hv_balloon.c index 53932b3..c5bb872 100644 --- a/drivers/hv/hv_balloon.c +++ b/drivers/hv/hv_balloon.c @@ -1138,6 +1138,8 @@ static void balloon_up(struct work_struct *dummy) bool alloc_error; bool done = false; int i; + struct sysinfo val; + unsigned long floor; /* The host balloons pages in 2M granularity. */ WARN_ON_ONCE(num_pages % PAGES_IN_2M != 0); @@ -1148,6 +1150,15 @@ static void balloon_up(struct work_struct *dummy) */ alloc_unit = 512; + si_meminfo(&val); + floor = compute_balloon_floor(); + + /* Refuse to balloon below the floor, keep the 2M granularity. */ + if (val.freeram - num_pages < floor) { + num_pages = val.freeram > floor ? (val.freeram - floor) : 0; + num_pages -= num_pages % PAGES_IN_2M; + } + while (!done) { bl_resp = (struct dm_balloon_response *)send_buffer; memset(send_buffer, 0, PAGE_SIZE); -- cgit v0.10.2 From 96c1d0581d00f7abe033350edb021a9d947d8d81 Mon Sep 17 00:00:00 2001 From: Nick Meier Date: Sat, 28 Feb 2015 11:39:01 -0800 Subject: Drivers: hv: vmbus: Add support for VMBus panic notifier handler Hyper-V allows a guest to notify the Hyper-V host that a panic condition occured. This notification can include up to five 64 bit values. These 64 bit values are written into crash MSRs. Once the data has been written into the crash MSRs, the host is then notified by writing into a Crash Control MSR. On the Hyper-V host, the panic notification data is captured in the Windows Event log as a 18590 event. Crash MSRs are defined in appendix H of the Hypervisor Top Level Functional Specification. At the time of this patch, v4.0 is the current functional spec. The URL for the v4.0 document is: http://download.microsoft.com/download/A/B/4/AB43A34E-BDD0-4FA6-BDEF-79EEF16E880B/Hypervisor Top Level Functional Specification v4.0.docx Signed-off-by: Nick Meier Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/hv/hyperv_vmbus.h b/drivers/hv/hyperv_vmbus.h index b055e53..88af4ec 100644 --- a/drivers/hv/hyperv_vmbus.h +++ b/drivers/hv/hyperv_vmbus.h @@ -49,6 +49,17 @@ enum hv_cpuid_function { HVCPUID_IMPLEMENTATION_LIMITS = 0x40000005, }; +#define HV_FEATURE_GUEST_CRASH_MSR_AVAILABLE 0x400 + +#define HV_X64_MSR_CRASH_P0 0x40000100 +#define HV_X64_MSR_CRASH_P1 0x40000101 +#define HV_X64_MSR_CRASH_P2 0x40000102 +#define HV_X64_MSR_CRASH_P3 0x40000103 +#define HV_X64_MSR_CRASH_P4 0x40000104 +#define HV_X64_MSR_CRASH_CTL 0x40000105 + +#define HV_CRASH_CTL_CRASH_NOTIFY 0x8000000000000000 + /* Define version of the synthetic interrupt controller. */ #define HV_SYNIC_VERSION (1) diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c index 2b7b51d..526fa8b 100644 --- a/drivers/hv/vmbus_drv.c +++ b/drivers/hv/vmbus_drv.c @@ -37,6 +37,8 @@ #include #include #include +#include +#include #include "hyperv_vmbus.h" static struct acpi_device *hv_acpi_dev; @@ -45,6 +47,31 @@ static struct tasklet_struct msg_dpc; static struct completion probe_event; static int irq; + +int hyperv_panic_event(struct notifier_block *nb, + unsigned long event, void *ptr) +{ + struct pt_regs *regs; + + regs = current_pt_regs(); + + wrmsrl(HV_X64_MSR_CRASH_P0, regs->ip); + wrmsrl(HV_X64_MSR_CRASH_P1, regs->ax); + wrmsrl(HV_X64_MSR_CRASH_P2, regs->bx); + wrmsrl(HV_X64_MSR_CRASH_P3, regs->cx); + wrmsrl(HV_X64_MSR_CRASH_P4, regs->dx); + + /* + * Let Hyper-V know there is crash data available + */ + wrmsrl(HV_X64_MSR_CRASH_CTL, HV_CRASH_CTL_CRASH_NOTIFY); + return NOTIFY_DONE; +} + +static struct notifier_block hyperv_panic_block = { + .notifier_call = hyperv_panic_event, +}; + struct resource hyperv_mmio = { .name = "hyperv mmio", .flags = IORESOURCE_MEM, @@ -795,6 +822,15 @@ static int vmbus_bus_init(int irq) goto err_alloc; hv_cpu_hotplug_quirk(true); + + /* + * Only register if the crash MSRs are available + */ + if (ms_hyperv.features & HV_FEATURE_GUEST_CRASH_MSR_AVAILABLE) { + atomic_notifier_chain_register(&panic_notifier_list, + &hyperv_panic_block); + } + vmbus_request_offers(); return 0; -- cgit v0.10.2 From a13e8bbe851a96a0e78c2bd599bc34082fa697cd Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Sat, 28 Feb 2015 11:39:02 -0800 Subject: Drivers: hv: vmbus: Use a round-robin algorithm for picking the outgoing channel The current algorithm for picking an outgoing channel was not distributing the load well. Implement a simple round-robin scheme to ensure good distribution of the outgoing traffic. Signed-off-by: K. Y. Srinivasan Reviewed-by: Long Li Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/hv/channel_mgmt.c b/drivers/hv/channel_mgmt.c index b1e5a5f..6117891 100644 --- a/drivers/hv/channel_mgmt.c +++ b/drivers/hv/channel_mgmt.c @@ -350,6 +350,7 @@ static void vmbus_process_offer(struct vmbus_channel *newchannel) } newchannel->state = CHANNEL_OPEN_STATE; + channel->num_sc++; if (channel->sc_creation_callback != NULL) /* * We need to invoke the sub-channel creation @@ -862,9 +863,8 @@ cleanup: /* * Retrieve the (sub) channel on which to send an outgoing request. - * When a primary channel has multiple sub-channels, we choose a - * channel whose VCPU binding is closest to the VCPU on which - * this call is being made. + * When a primary channel has multiple sub-channels, we try to + * distribute the load equally amongst all available channels. */ struct vmbus_channel *vmbus_get_outgoing_channel(struct vmbus_channel *primary) { @@ -872,11 +872,19 @@ struct vmbus_channel *vmbus_get_outgoing_channel(struct vmbus_channel *primary) int cur_cpu; struct vmbus_channel *cur_channel; struct vmbus_channel *outgoing_channel = primary; - int cpu_distance, new_cpu_distance; + int next_channel; + int i = 1; if (list_empty(&primary->sc_list)) return outgoing_channel; + next_channel = primary->next_oc++; + + if (next_channel > (primary->num_sc)) { + primary->next_oc = 0; + return outgoing_channel; + } + cur_cpu = hv_context.vp_index[get_cpu()]; put_cpu(); list_for_each_safe(cur, tmp, &primary->sc_list) { @@ -887,18 +895,10 @@ struct vmbus_channel *vmbus_get_outgoing_channel(struct vmbus_channel *primary) if (cur_channel->target_vp == cur_cpu) return cur_channel; - cpu_distance = ((outgoing_channel->target_vp > cur_cpu) ? - (outgoing_channel->target_vp - cur_cpu) : - (cur_cpu - outgoing_channel->target_vp)); - - new_cpu_distance = ((cur_channel->target_vp > cur_cpu) ? - (cur_channel->target_vp - cur_cpu) : - (cur_cpu - cur_channel->target_vp)); - - if (cpu_distance < new_cpu_distance) - continue; + if (i == next_channel) + return cur_channel; - outgoing_channel = cur_channel; + i++; } return outgoing_channel; diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index dd92a85..1ca5824 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -761,6 +761,9 @@ struct vmbus_channel { * link up channels based on their CPU affinity. */ struct list_head percpu_list; + + int num_sc; + int next_oc; }; static inline void set_channel_read_state(struct vmbus_channel *c, bool state) -- cgit v0.10.2 From 87e93d61708fe2c44875d1ecdb174aad070dbd08 Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Sat, 28 Feb 2015 11:39:03 -0800 Subject: Drivers: hv: vmbus: Suport an API to send pagebuffers with additional control Implement an API for sending pagebuffers that gives more control to the client in terms of setting the vmbus flags as well as deciding when to notify the host. This will be useful for enabling batch processing. Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/hv/channel.c b/drivers/hv/channel.c index 9b79aca..f060d1f 100644 --- a/drivers/hv/channel.c +++ b/drivers/hv/channel.c @@ -636,13 +636,18 @@ int vmbus_sendpacket(struct vmbus_channel *channel, void *buffer, EXPORT_SYMBOL(vmbus_sendpacket); /* - * vmbus_sendpacket_pagebuffer - Send a range of single-page buffer - * packets using a GPADL Direct packet type. + * vmbus_sendpacket_pagebuffer_ctl - Send a range of single-page buffer + * packets using a GPADL Direct packet type. This interface allows you + * to control notifying the host. This will be useful for sending + * batched data. Also the sender can control the send flags + * explicitly. */ -int vmbus_sendpacket_pagebuffer(struct vmbus_channel *channel, +int vmbus_sendpacket_pagebuffer_ctl(struct vmbus_channel *channel, struct hv_page_buffer pagebuffers[], u32 pagecount, void *buffer, u32 bufferlen, - u64 requestid) + u64 requestid, + u32 flags, + bool kick_q) { int ret; int i; @@ -670,7 +675,7 @@ int vmbus_sendpacket_pagebuffer(struct vmbus_channel *channel, /* Setup the descriptor */ desc.type = VM_PKT_DATA_USING_GPA_DIRECT; - desc.flags = VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED; + desc.flags = flags; desc.dataoffset8 = descsize >> 3; /* in 8-bytes grandularity */ desc.length8 = (u16)(packetlen_aligned >> 3); desc.transactionid = requestid; @@ -691,11 +696,27 @@ int vmbus_sendpacket_pagebuffer(struct vmbus_channel *channel, ret = hv_ringbuffer_write(&channel->outbound, bufferlist, 3, &signal); - if (ret == 0 && signal) + if ((ret == 0) && kick_q && signal) vmbus_setevent(channel); return ret; } + +/* + * vmbus_sendpacket_pagebuffer - Send a range of single-page buffer + * packets using a GPADL Direct packet type. + */ +int vmbus_sendpacket_pagebuffer(struct vmbus_channel *channel, + struct hv_page_buffer pagebuffers[], + u32 pagecount, void *buffer, u32 bufferlen, + u64 requestid) +{ + u32 flags = VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED; + return vmbus_sendpacket_pagebuffer_ctl(channel, pagebuffers, pagecount, + buffer, bufferlen, requestid, + flags, true); + +} EXPORT_SYMBOL_GPL(vmbus_sendpacket_pagebuffer); /* diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index 1ca5824..86e1a7a 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -874,6 +874,15 @@ extern int vmbus_sendpacket_pagebuffer(struct vmbus_channel *channel, u32 bufferlen, u64 requestid); +extern int vmbus_sendpacket_pagebuffer_ctl(struct vmbus_channel *channel, + struct hv_page_buffer pagebuffers[], + u32 pagecount, + void *buffer, + u32 bufferlen, + u64 requestid, + u32 flags, + bool kick_q); + extern int vmbus_sendpacket_multipagebuffer(struct vmbus_channel *channel, struct hv_multipage_buffer *mpb, void *buffer, -- cgit v0.10.2 From e9395e3f8952110bda60b54ad03ec52c6e9c7dbd Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Sat, 28 Feb 2015 11:39:04 -0800 Subject: Drivers: hv: vmbus: Suport an API to send packet with additional control Implement an API that gives additional control on the what VMBUS flags will be set as well as if the host needs to be signalled. This API will be useful for clients that want to batch up requests to the host. Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/hv/channel.c b/drivers/hv/channel.c index f060d1f..da53180 100644 --- a/drivers/hv/channel.c +++ b/drivers/hv/channel.c @@ -584,23 +584,9 @@ void vmbus_close(struct vmbus_channel *channel) } EXPORT_SYMBOL_GPL(vmbus_close); -/** - * vmbus_sendpacket() - Send the specified buffer on the given channel - * @channel: Pointer to vmbus_channel structure. - * @buffer: Pointer to the buffer you want to receive the data into. - * @bufferlen: Maximum size of what the the buffer will hold - * @requestid: Identifier of the request - * @type: Type of packet that is being send e.g. negotiate, time - * packet etc. - * - * Sends data in @buffer directly to hyper-v via the vmbus - * This will send the data unparsed to hyper-v. - * - * Mainly used by Hyper-V drivers. - */ -int vmbus_sendpacket(struct vmbus_channel *channel, void *buffer, +int vmbus_sendpacket_ctl(struct vmbus_channel *channel, void *buffer, u32 bufferlen, u64 requestid, - enum vmbus_packet_type type, u32 flags) + enum vmbus_packet_type type, u32 flags, bool kick_q) { struct vmpacket_descriptor desc; u32 packetlen = sizeof(struct vmpacket_descriptor) + bufferlen; @@ -628,11 +614,34 @@ int vmbus_sendpacket(struct vmbus_channel *channel, void *buffer, ret = hv_ringbuffer_write(&channel->outbound, bufferlist, 3, &signal); - if (ret == 0 && signal) + if ((ret == 0) && kick_q && signal) vmbus_setevent(channel); return ret; } +EXPORT_SYMBOL(vmbus_sendpacket_ctl); + +/** + * vmbus_sendpacket() - Send the specified buffer on the given channel + * @channel: Pointer to vmbus_channel structure. + * @buffer: Pointer to the buffer you want to receive the data into. + * @bufferlen: Maximum size of what the the buffer will hold + * @requestid: Identifier of the request + * @type: Type of packet that is being send e.g. negotiate, time + * packet etc. + * + * Sends data in @buffer directly to hyper-v via the vmbus + * This will send the data unparsed to hyper-v. + * + * Mainly used by Hyper-V drivers. + */ +int vmbus_sendpacket(struct vmbus_channel *channel, void *buffer, + u32 bufferlen, u64 requestid, + enum vmbus_packet_type type, u32 flags) +{ + return vmbus_sendpacket_ctl(channel, buffer, bufferlen, requestid, + type, flags, true); +} EXPORT_SYMBOL(vmbus_sendpacket); /* diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index 86e1a7a..80e444b 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -867,6 +867,14 @@ extern int vmbus_sendpacket(struct vmbus_channel *channel, enum vmbus_packet_type type, u32 flags); +extern int vmbus_sendpacket_ctl(struct vmbus_channel *channel, + void *buffer, + u32 bufferLen, + u64 requestid, + enum vmbus_packet_type type, + u32 flags, + bool kick_q); + extern int vmbus_sendpacket_pagebuffer(struct vmbus_channel *channel, struct hv_page_buffer pagebuffers[], u32 pagecount, -- cgit v0.10.2 From b7d885145538ddedb1ae23b782ab7c7c0a856e9f Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 10 Feb 2015 10:39:31 +0200 Subject: mei: revamp me clients list handling 1. Use rw lock to access the me_clients list 2. Reuse already defined find functions also when removing particular me client 3. Add wrappers for addition and deletion Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c index dfbddfe..48813c2 100644 --- a/drivers/misc/mei/client.c +++ b/drivers/misc/mei/client.c @@ -48,14 +48,14 @@ void mei_me_cl_init(struct mei_me_client *me_cl) */ struct mei_me_client *mei_me_cl_get(struct mei_me_client *me_cl) { - if (me_cl) - kref_get(&me_cl->refcnt); + if (me_cl && kref_get_unless_zero(&me_cl->refcnt)) + return me_cl; - return me_cl; + return NULL; } /** - * mei_me_cl_release - unlink and free me client + * mei_me_cl_release - free me client * * Locking: called under "dev->device_lock" lock * @@ -65,9 +65,10 @@ static void mei_me_cl_release(struct kref *ref) { struct mei_me_client *me_cl = container_of(ref, struct mei_me_client, refcnt); - list_del(&me_cl->list); + kfree(me_cl); } + /** * mei_me_cl_put - decrease me client refcount and free client if necessary * @@ -82,51 +83,146 @@ void mei_me_cl_put(struct mei_me_client *me_cl) } /** - * mei_me_cl_by_uuid - locate me client by uuid + * __mei_me_cl_del - delete me client form the list and decrease + * reference counter + * + * @dev: mei device + * @me_cl: me client + * + * Locking: dev->me_clients_rwsem + */ +static void __mei_me_cl_del(struct mei_device *dev, struct mei_me_client *me_cl) +{ + if (!me_cl) + return; + + list_del(&me_cl->list); + mei_me_cl_put(me_cl); +} + +/** + * mei_me_cl_add - add me client to the list + * + * @dev: mei device + * @me_cl: me client + */ +void mei_me_cl_add(struct mei_device *dev, struct mei_me_client *me_cl) +{ + down_write(&dev->me_clients_rwsem); + list_add(&me_cl->list, &dev->me_clients); + up_write(&dev->me_clients_rwsem); +} + +/** + * __mei_me_cl_by_uuid - locate me client by uuid * increases ref count * * @dev: mei device * @uuid: me client uuid * - * Locking: called under "dev->device_lock" lock - * * Return: me client or NULL if not found + * + * Locking: dev->me_clients_rwsem */ -struct mei_me_client *mei_me_cl_by_uuid(const struct mei_device *dev, +static struct mei_me_client *__mei_me_cl_by_uuid(struct mei_device *dev, const uuid_le *uuid) { struct mei_me_client *me_cl; + const uuid_le *pn; + + WARN_ON(!rwsem_is_locked(&dev->me_clients_rwsem)); - list_for_each_entry(me_cl, &dev->me_clients, list) - if (uuid_le_cmp(*uuid, me_cl->props.protocol_name) == 0) + list_for_each_entry(me_cl, &dev->me_clients, list) { + pn = &me_cl->props.protocol_name; + if (uuid_le_cmp(*uuid, *pn) == 0) return mei_me_cl_get(me_cl); + } return NULL; } /** + * mei_me_cl_by_uuid - locate me client by uuid + * increases ref count + * + * @dev: mei device + * @uuid: me client uuid + * + * Return: me client or NULL if not found + * + * Locking: dev->me_clients_rwsem + */ +struct mei_me_client *mei_me_cl_by_uuid(struct mei_device *dev, + const uuid_le *uuid) +{ + struct mei_me_client *me_cl; + + down_read(&dev->me_clients_rwsem); + me_cl = __mei_me_cl_by_uuid(dev, uuid); + up_read(&dev->me_clients_rwsem); + + return me_cl; +} + +/** * mei_me_cl_by_id - locate me client by client id * increases ref count * * @dev: the device structure * @client_id: me client id * - * Locking: called under "dev->device_lock" lock - * * Return: me client or NULL if not found + * + * Locking: dev->me_clients_rwsem */ struct mei_me_client *mei_me_cl_by_id(struct mei_device *dev, u8 client_id) { + struct mei_me_client *__me_cl, *me_cl = NULL; + + down_read(&dev->me_clients_rwsem); + list_for_each_entry(__me_cl, &dev->me_clients, list) { + if (__me_cl->client_id == client_id) { + me_cl = mei_me_cl_get(__me_cl); + break; + } + } + up_read(&dev->me_clients_rwsem); + + return me_cl; +} + +/** + * __mei_me_cl_by_uuid_id - locate me client by client id and uuid + * increases ref count + * + * @dev: the device structure + * @uuid: me client uuid + * @client_id: me client id + * + * Return: me client or null if not found + * + * Locking: dev->me_clients_rwsem + */ +static struct mei_me_client *__mei_me_cl_by_uuid_id(struct mei_device *dev, + const uuid_le *uuid, u8 client_id) +{ struct mei_me_client *me_cl; + const uuid_le *pn; + + WARN_ON(!rwsem_is_locked(&dev->me_clients_rwsem)); - list_for_each_entry(me_cl, &dev->me_clients, list) - if (me_cl->client_id == client_id) + list_for_each_entry(me_cl, &dev->me_clients, list) { + pn = &me_cl->props.protocol_name; + if (uuid_le_cmp(*uuid, *pn) == 0 && + me_cl->client_id == client_id) return mei_me_cl_get(me_cl); + } return NULL; } + /** * mei_me_cl_by_uuid_id - locate me client by client id and uuid * increases ref count @@ -135,21 +231,18 @@ struct mei_me_client *mei_me_cl_by_id(struct mei_device *dev, u8 client_id) * @uuid: me client uuid * @client_id: me client id * - * Locking: called under "dev->device_lock" lock - * - * Return: me client or NULL if not found + * Return: me client or null if not found */ struct mei_me_client *mei_me_cl_by_uuid_id(struct mei_device *dev, const uuid_le *uuid, u8 client_id) { struct mei_me_client *me_cl; - list_for_each_entry(me_cl, &dev->me_clients, list) - if (uuid_le_cmp(*uuid, me_cl->props.protocol_name) == 0 && - me_cl->client_id == client_id) - return mei_me_cl_get(me_cl); + down_read(&dev->me_clients_rwsem); + me_cl = __mei_me_cl_by_uuid_id(dev, uuid, client_id); + up_read(&dev->me_clients_rwsem); - return NULL; + return me_cl; } /** @@ -162,12 +255,14 @@ struct mei_me_client *mei_me_cl_by_uuid_id(struct mei_device *dev, */ void mei_me_cl_rm_by_uuid(struct mei_device *dev, const uuid_le *uuid) { - struct mei_me_client *me_cl, *next; + struct mei_me_client *me_cl; dev_dbg(dev->dev, "remove %pUl\n", uuid); - list_for_each_entry_safe(me_cl, next, &dev->me_clients, list) - if (uuid_le_cmp(*uuid, me_cl->props.protocol_name) == 0) - mei_me_cl_put(me_cl); + + down_write(&dev->me_clients_rwsem); + me_cl = __mei_me_cl_by_uuid(dev, uuid); + __mei_me_cl_del(dev, me_cl); + up_write(&dev->me_clients_rwsem); } /** @@ -181,15 +276,14 @@ void mei_me_cl_rm_by_uuid(struct mei_device *dev, const uuid_le *uuid) */ void mei_me_cl_rm_by_uuid_id(struct mei_device *dev, const uuid_le *uuid, u8 id) { - struct mei_me_client *me_cl, *next; - const uuid_le *pn; + struct mei_me_client *me_cl; dev_dbg(dev->dev, "remove %pUl %d\n", uuid, id); - list_for_each_entry_safe(me_cl, next, &dev->me_clients, list) { - pn = &me_cl->props.protocol_name; - if (me_cl->client_id == id && uuid_le_cmp(*uuid, *pn) == 0) - mei_me_cl_put(me_cl); - } + + down_write(&dev->me_clients_rwsem); + me_cl = __mei_me_cl_by_uuid_id(dev, uuid, id); + __mei_me_cl_del(dev, me_cl); + up_write(&dev->me_clients_rwsem); } /** @@ -203,12 +297,12 @@ void mei_me_cl_rm_all(struct mei_device *dev) { struct mei_me_client *me_cl, *next; + down_write(&dev->me_clients_rwsem); list_for_each_entry_safe(me_cl, next, &dev->me_clients, list) - mei_me_cl_put(me_cl); + __mei_me_cl_del(dev, me_cl); + up_write(&dev->me_clients_rwsem); } - - /** * mei_cl_cmp_id - tells if the clients are the same * @@ -535,28 +629,28 @@ int mei_cl_unlink(struct mei_cl *cl) void mei_host_client_init(struct work_struct *work) { - struct mei_device *dev = container_of(work, - struct mei_device, init_work); + struct mei_device *dev = + container_of(work, struct mei_device, init_work); struct mei_me_client *me_cl; - struct mei_client_properties *props; mutex_lock(&dev->device_lock); - list_for_each_entry(me_cl, &dev->me_clients, list) { - props = &me_cl->props; - if (!uuid_le_cmp(props->protocol_name, mei_amthif_guid)) - mei_amthif_host_init(dev); - else if (!uuid_le_cmp(props->protocol_name, mei_wd_guid)) - mei_wd_host_init(dev); - else if (!uuid_le_cmp(props->protocol_name, mei_nfc_guid)) - mei_nfc_host_init(dev); + me_cl = mei_me_cl_by_uuid(dev, &mei_amthif_guid); + if (me_cl) + mei_amthif_host_init(dev); + + me_cl = mei_me_cl_by_uuid(dev, &mei_wd_guid); + if (me_cl) + mei_wd_host_init(dev); + + me_cl = mei_me_cl_by_uuid(dev, &mei_nfc_guid); + if (me_cl) + mei_nfc_host_init(dev); - } dev->dev_state = MEI_DEV_ENABLED; dev->reset_count = 0; - mutex_unlock(&dev->device_lock); pm_runtime_mark_last_busy(dev->dev); diff --git a/drivers/misc/mei/client.h b/drivers/misc/mei/client.h index cfcde8e..80386f9 100644 --- a/drivers/misc/mei/client.h +++ b/drivers/misc/mei/client.h @@ -31,7 +31,10 @@ void mei_me_cl_init(struct mei_me_client *me_cl); void mei_me_cl_put(struct mei_me_client *me_cl); struct mei_me_client *mei_me_cl_get(struct mei_me_client *me_cl); -struct mei_me_client *mei_me_cl_by_uuid(const struct mei_device *dev, +void mei_me_cl_add(struct mei_device *dev, struct mei_me_client *me_cl); +void mei_me_cl_del(struct mei_device *dev, struct mei_me_client *me_cl); + +struct mei_me_client *mei_me_cl_by_uuid(struct mei_device *dev, const uuid_le *uuid); struct mei_me_client *mei_me_cl_by_id(struct mei_device *dev, u8 client_id); struct mei_me_client *mei_me_cl_by_uuid_id(struct mei_device *dev, diff --git a/drivers/misc/mei/debugfs.c b/drivers/misc/mei/debugfs.c index b125380..50fc663 100644 --- a/drivers/misc/mei/debugfs.c +++ b/drivers/misc/mei/debugfs.c @@ -28,7 +28,7 @@ static ssize_t mei_dbgfs_read_meclients(struct file *fp, char __user *ubuf, size_t cnt, loff_t *ppos) { struct mei_device *dev = fp->private_data; - struct mei_me_client *me_cl, *n; + struct mei_me_client *me_cl; size_t bufsz = 1; char *buf; int i = 0; @@ -38,15 +38,14 @@ static ssize_t mei_dbgfs_read_meclients(struct file *fp, char __user *ubuf, #define HDR \ " |id|fix| UUID |con|msg len|sb|refc|\n" - mutex_lock(&dev->device_lock); - + down_read(&dev->me_clients_rwsem); list_for_each_entry(me_cl, &dev->me_clients, list) bufsz++; bufsz *= sizeof(HDR) + 1; buf = kzalloc(bufsz, GFP_KERNEL); if (!buf) { - mutex_unlock(&dev->device_lock); + up_read(&dev->me_clients_rwsem); return -ENOMEM; } @@ -56,10 +55,9 @@ static ssize_t mei_dbgfs_read_meclients(struct file *fp, char __user *ubuf, if (dev->dev_state != MEI_DEV_ENABLED) goto out; - list_for_each_entry_safe(me_cl, n, &dev->me_clients, list) { + list_for_each_entry(me_cl, &dev->me_clients, list) { - me_cl = mei_me_cl_get(me_cl); - if (me_cl) { + if (mei_me_cl_get(me_cl)) { pos += scnprintf(buf + pos, bufsz - pos, "%2d|%2d|%3d|%pUl|%3d|%7d|%2d|%4d|\n", i++, me_cl->client_id, @@ -69,12 +67,13 @@ static ssize_t mei_dbgfs_read_meclients(struct file *fp, char __user *ubuf, me_cl->props.max_msg_length, me_cl->props.single_recv_buf, atomic_read(&me_cl->refcnt.refcount)); - } - mei_me_cl_put(me_cl); + mei_me_cl_put(me_cl); + } } + out: - mutex_unlock(&dev->device_lock); + up_read(&dev->me_clients_rwsem); ret = simple_read_from_buffer(ubuf, cnt, ppos, buf, pos); kfree(buf); return ret; diff --git a/drivers/misc/mei/hbm.c b/drivers/misc/mei/hbm.c index c8412d4..4f83e9a 100644 --- a/drivers/misc/mei/hbm.c +++ b/drivers/misc/mei/hbm.c @@ -338,7 +338,8 @@ static int mei_hbm_me_cl_add(struct mei_device *dev, me_cl->client_id = res->me_addr; me_cl->mei_flow_ctrl_creds = 0; - list_add(&me_cl->list, &dev->me_clients); + mei_me_cl_add(dev, me_cl); + return 0; } diff --git a/drivers/misc/mei/init.c b/drivers/misc/mei/init.c index 9306219..106c054 100644 --- a/drivers/misc/mei/init.c +++ b/drivers/misc/mei/init.c @@ -387,6 +387,7 @@ void mei_device_init(struct mei_device *dev, INIT_LIST_HEAD(&dev->device_list); INIT_LIST_HEAD(&dev->me_clients); mutex_init(&dev->device_lock); + init_rwsem(&dev->me_clients_rwsem); init_waitqueue_head(&dev->wait_hw_ready); init_waitqueue_head(&dev->wait_pg); init_waitqueue_head(&dev->wait_hbm_start); diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index 6c6ce93..102cc66 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -460,6 +460,7 @@ const char *mei_pg_state_str(enum mei_pg_state state); * @version : HBM protocol version in use * @hbm_f_pg_supported : hbm feature pgi protocol * + * @me_clients_rwsem: rw lock over me_clients list * @me_clients : list of FW clients * @me_clients_map : FW clients bit map * @host_clients_map : host clients id pool @@ -556,6 +557,7 @@ struct mei_device { struct hbm_version version; unsigned int hbm_f_pg_supported:1; + struct rw_semaphore me_clients_rwsem; struct list_head me_clients; DECLARE_BITMAP(me_clients_map, MEI_CLIENTS_MAX); DECLARE_BITMAP(host_clients_map, MEI_CLIENTS_MAX); -- cgit v0.10.2 From 381a58c70985ca1256b0f51aa6694f79662bb166 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 10 Feb 2015 10:39:32 +0200 Subject: mei: me: use io register wrappers consistently 1. Use mei_device structure as the first argument to the io register access wrappers so we'll have access to the device structure needed for tracing. 2. Use wrapper consistently Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/misc/mei/hw-me.c b/drivers/misc/mei/hw-me.c index f8fd503..ac82b56 100644 --- a/drivers/misc/mei/hw-me.c +++ b/drivers/misc/mei/hw-me.c @@ -61,45 +61,68 @@ static inline void mei_me_reg_write(const struct mei_me_hw *hw, * * Return: ME_CB_RW register value (u32) */ -static u32 mei_me_mecbrw_read(const struct mei_device *dev) +static inline u32 mei_me_mecbrw_read(const struct mei_device *dev) { return mei_me_reg_read(to_me_hw(dev), ME_CB_RW); } + +/** + * mei_me_hcbww_write - write 32bit data to the host circular buffer + * + * @dev: the device structure + * @data: 32bit data to be written to the host circular buffer + */ +static inline void mei_me_hcbww_write(struct mei_device *dev, u32 data) +{ + mei_me_reg_write(to_me_hw(dev), H_CB_WW, data); +} + /** * mei_me_mecsr_read - Reads 32bit data from the ME CSR * - * @hw: the me hardware structure + * @dev: the device structure * * Return: ME_CSR_HA register value (u32) */ -static inline u32 mei_me_mecsr_read(const struct mei_me_hw *hw) +static inline u32 mei_me_mecsr_read(const struct mei_device *dev) { - return mei_me_reg_read(hw, ME_CSR_HA); + return mei_me_reg_read(to_me_hw(dev), ME_CSR_HA); } /** * mei_hcsr_read - Reads 32bit data from the host CSR * - * @hw: the me hardware structure + * @dev: the device structure * * Return: H_CSR register value (u32) */ -static inline u32 mei_hcsr_read(const struct mei_me_hw *hw) +static inline u32 mei_hcsr_read(const struct mei_device *dev) { - return mei_me_reg_read(hw, H_CSR); + return mei_me_reg_read(to_me_hw(dev), H_CSR); +} + +/** + * mei_hcsr_write - writes H_CSR register to the mei device + * + * @dev: the device structure + * @reg: new register value + */ +static inline void mei_hcsr_write(struct mei_device *dev, u32 reg) +{ + mei_me_reg_write(to_me_hw(dev), H_CSR, reg); } /** * mei_hcsr_set - writes H_CSR register to the mei device, * and ignores the H_IS bit for it is write-one-to-zero. * - * @hw: the me hardware structure - * @hcsr: new register value + * @dev: the device structure + * @reg: new register value */ -static inline void mei_hcsr_set(struct mei_me_hw *hw, u32 hcsr) +static inline void mei_hcsr_set(struct mei_device *dev, u32 reg) { - hcsr &= ~H_IS; - mei_me_reg_write(hw, H_CSR, hcsr); + reg &= ~H_IS; + mei_hcsr_write(dev, reg); } /** @@ -141,7 +164,7 @@ static int mei_me_fw_status(struct mei_device *dev, static void mei_me_hw_config(struct mei_device *dev) { struct mei_me_hw *hw = to_me_hw(dev); - u32 hcsr = mei_hcsr_read(to_me_hw(dev)); + u32 hcsr = mei_hcsr_read(dev); /* Doesn't change in runtime */ dev->hbuf_depth = (hcsr & H_CBD) >> 24; @@ -170,11 +193,10 @@ static inline enum mei_pg_state mei_me_pg_state(struct mei_device *dev) */ static void mei_me_intr_clear(struct mei_device *dev) { - struct mei_me_hw *hw = to_me_hw(dev); - u32 hcsr = mei_hcsr_read(hw); + u32 hcsr = mei_hcsr_read(dev); if ((hcsr & H_IS) == H_IS) - mei_me_reg_write(hw, H_CSR, hcsr); + mei_hcsr_write(dev, hcsr); } /** * mei_me_intr_enable - enables mei device interrupts @@ -183,11 +205,10 @@ static void mei_me_intr_clear(struct mei_device *dev) */ static void mei_me_intr_enable(struct mei_device *dev) { - struct mei_me_hw *hw = to_me_hw(dev); - u32 hcsr = mei_hcsr_read(hw); + u32 hcsr = mei_hcsr_read(dev); hcsr |= H_IE; - mei_hcsr_set(hw, hcsr); + mei_hcsr_set(dev, hcsr); } /** @@ -197,11 +218,10 @@ static void mei_me_intr_enable(struct mei_device *dev) */ static void mei_me_intr_disable(struct mei_device *dev) { - struct mei_me_hw *hw = to_me_hw(dev); - u32 hcsr = mei_hcsr_read(hw); + u32 hcsr = mei_hcsr_read(dev); hcsr &= ~H_IE; - mei_hcsr_set(hw, hcsr); + mei_hcsr_set(dev, hcsr); } /** @@ -211,12 +231,11 @@ static void mei_me_intr_disable(struct mei_device *dev) */ static void mei_me_hw_reset_release(struct mei_device *dev) { - struct mei_me_hw *hw = to_me_hw(dev); - u32 hcsr = mei_hcsr_read(hw); + u32 hcsr = mei_hcsr_read(dev); hcsr |= H_IG; hcsr &= ~H_RST; - mei_hcsr_set(hw, hcsr); + mei_hcsr_set(dev, hcsr); /* complete this write before we set host ready on another CPU */ mmiowb(); @@ -231,8 +250,7 @@ static void mei_me_hw_reset_release(struct mei_device *dev) */ static int mei_me_hw_reset(struct mei_device *dev, bool intr_enable) { - struct mei_me_hw *hw = to_me_hw(dev); - u32 hcsr = mei_hcsr_read(hw); + u32 hcsr = mei_hcsr_read(dev); /* H_RST may be found lit before reset is started, * for example if preceding reset flow hasn't completed. @@ -242,8 +260,8 @@ static int mei_me_hw_reset(struct mei_device *dev, bool intr_enable) if ((hcsr & H_RST) == H_RST) { dev_warn(dev->dev, "H_RST is set = 0x%08X", hcsr); hcsr &= ~H_RST; - mei_hcsr_set(hw, hcsr); - hcsr = mei_hcsr_read(hw); + mei_hcsr_set(dev, hcsr); + hcsr = mei_hcsr_read(dev); } hcsr |= H_RST | H_IG | H_IS; @@ -254,13 +272,13 @@ static int mei_me_hw_reset(struct mei_device *dev, bool intr_enable) hcsr &= ~H_IE; dev->recvd_hw_ready = false; - mei_me_reg_write(hw, H_CSR, hcsr); + mei_hcsr_write(dev, hcsr); /* * Host reads the H_CSR once to ensure that the * posted write to H_CSR completes. */ - hcsr = mei_hcsr_read(hw); + hcsr = mei_hcsr_read(dev); if ((hcsr & H_RST) == 0) dev_warn(dev->dev, "H_RST is not set = 0x%08X", hcsr); @@ -281,11 +299,10 @@ static int mei_me_hw_reset(struct mei_device *dev, bool intr_enable) */ static void mei_me_host_set_ready(struct mei_device *dev) { - struct mei_me_hw *hw = to_me_hw(dev); - u32 hcsr = mei_hcsr_read(hw); + u32 hcsr = mei_hcsr_read(dev); hcsr |= H_IE | H_IG | H_RDY; - mei_hcsr_set(hw, hcsr); + mei_hcsr_set(dev, hcsr); } /** @@ -296,8 +313,7 @@ static void mei_me_host_set_ready(struct mei_device *dev) */ static bool mei_me_host_is_ready(struct mei_device *dev) { - struct mei_me_hw *hw = to_me_hw(dev); - u32 hcsr = mei_hcsr_read(hw); + u32 hcsr = mei_hcsr_read(dev); return (hcsr & H_RDY) == H_RDY; } @@ -310,8 +326,7 @@ static bool mei_me_host_is_ready(struct mei_device *dev) */ static bool mei_me_hw_is_ready(struct mei_device *dev) { - struct mei_me_hw *hw = to_me_hw(dev); - u32 mecsr = mei_me_mecsr_read(hw); + u32 mecsr = mei_me_mecsr_read(dev); return (mecsr & ME_RDY_HRA) == ME_RDY_HRA; } @@ -368,11 +383,10 @@ static int mei_me_hw_start(struct mei_device *dev) */ static unsigned char mei_hbuf_filled_slots(struct mei_device *dev) { - struct mei_me_hw *hw = to_me_hw(dev); u32 hcsr; char read_ptr, write_ptr; - hcsr = mei_hcsr_read(hw); + hcsr = mei_hcsr_read(dev); read_ptr = (char) ((hcsr & H_CBRP) >> 8); write_ptr = (char) ((hcsr & H_CBWP) >> 16); @@ -439,7 +453,6 @@ static int mei_me_write_message(struct mei_device *dev, struct mei_msg_hdr *header, unsigned char *buf) { - struct mei_me_hw *hw = to_me_hw(dev); unsigned long rem; unsigned long length = header->length; u32 *reg_buf = (u32 *)buf; @@ -457,21 +470,21 @@ static int mei_me_write_message(struct mei_device *dev, if (empty_slots < 0 || dw_cnt > empty_slots) return -EMSGSIZE; - mei_me_reg_write(hw, H_CB_WW, *((u32 *) header)); + mei_me_hcbww_write(dev, *((u32 *) header)); for (i = 0; i < length / 4; i++) - mei_me_reg_write(hw, H_CB_WW, reg_buf[i]); + mei_me_hcbww_write(dev, reg_buf[i]); rem = length & 0x3; if (rem > 0) { u32 reg = 0; memcpy(®, &buf[length - rem], rem); - mei_me_reg_write(hw, H_CB_WW, reg); + mei_me_hcbww_write(dev, reg); } - hcsr = mei_hcsr_read(hw) | H_IG; - mei_hcsr_set(hw, hcsr); + hcsr = mei_hcsr_read(dev) | H_IG; + mei_hcsr_set(dev, hcsr); if (!mei_me_hw_is_ready(dev)) return -EIO; @@ -487,12 +500,11 @@ static int mei_me_write_message(struct mei_device *dev, */ static int mei_me_count_full_read_slots(struct mei_device *dev) { - struct mei_me_hw *hw = to_me_hw(dev); u32 me_csr; char read_ptr, write_ptr; unsigned char buffer_depth, filled_slots; - me_csr = mei_me_mecsr_read(hw); + me_csr = mei_me_mecsr_read(dev); buffer_depth = (unsigned char)((me_csr & ME_CBD_HRA) >> 24); read_ptr = (char) ((me_csr & ME_CBRP_HRA) >> 8); write_ptr = (char) ((me_csr & ME_CBWP_HRA) >> 16); @@ -518,7 +530,6 @@ static int mei_me_count_full_read_slots(struct mei_device *dev) static int mei_me_read_slots(struct mei_device *dev, unsigned char *buffer, unsigned long buffer_length) { - struct mei_me_hw *hw = to_me_hw(dev); u32 *reg_buf = (u32 *)buffer; u32 hcsr; @@ -531,8 +542,8 @@ static int mei_me_read_slots(struct mei_device *dev, unsigned char *buffer, memcpy(reg_buf, ®, buffer_length); } - hcsr = mei_hcsr_read(hw) | H_IG; - mei_hcsr_set(hw, hcsr); + hcsr = mei_hcsr_read(dev) | H_IG; + mei_hcsr_set(dev, hcsr); return 0; } @@ -649,8 +660,7 @@ reply: */ static bool mei_me_pg_is_enabled(struct mei_device *dev) { - struct mei_me_hw *hw = to_me_hw(dev); - u32 reg = mei_me_reg_read(hw, ME_CSR_HA); + u32 reg = mei_me_mecsr_read(dev); if ((reg & ME_PGIC_HRA) == 0) goto notsupported; @@ -683,14 +693,13 @@ notsupported: irqreturn_t mei_me_irq_quick_handler(int irq, void *dev_id) { struct mei_device *dev = (struct mei_device *) dev_id; - struct mei_me_hw *hw = to_me_hw(dev); - u32 csr_reg = mei_hcsr_read(hw); + u32 hcsr = mei_hcsr_read(dev); - if ((csr_reg & H_IS) != H_IS) + if ((hcsr & H_IS) != H_IS) return IRQ_NONE; /* clear H_IS bit in H_CSR */ - mei_me_reg_write(hw, H_CSR, csr_reg); + mei_hcsr_write(dev, hcsr); return IRQ_WAKE_THREAD; } -- cgit v0.10.2 From a0a927d06d79d59c55ae7ac0b2fd7f3c0ea3c14c Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 10 Feb 2015 10:39:33 +0200 Subject: mei: me: add io register tracing To make debugging a bit easier we add me register access tracing /tracing/events/mei/mei_reg_{read,write} Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/misc/mei/Makefile b/drivers/misc/mei/Makefile index 8ebc6cd..518914a 100644 --- a/drivers/misc/mei/Makefile +++ b/drivers/misc/mei/Makefile @@ -21,3 +21,6 @@ mei-me-objs += hw-me.o obj-$(CONFIG_INTEL_MEI_TXE) += mei-txe.o mei-txe-objs := pci-txe.o mei-txe-objs += hw-txe.o + +mei-$(CONFIG_EVENT_TRACING) += mei-trace.o +CFLAGS_mei-trace.o = -I$(src) diff --git a/drivers/misc/mei/hw-me.c b/drivers/misc/mei/hw-me.c index ac82b56..d3aef82 100644 --- a/drivers/misc/mei/hw-me.c +++ b/drivers/misc/mei/hw-me.c @@ -25,6 +25,8 @@ #include "hw-me.h" #include "hw-me-regs.h" +#include "mei-trace.h" + /** * mei_me_reg_read - Reads 32bit data from the mei device * @@ -86,7 +88,12 @@ static inline void mei_me_hcbww_write(struct mei_device *dev, u32 data) */ static inline u32 mei_me_mecsr_read(const struct mei_device *dev) { - return mei_me_reg_read(to_me_hw(dev), ME_CSR_HA); + u32 reg; + + reg = mei_me_reg_read(to_me_hw(dev), ME_CSR_HA); + trace_mei_reg_read(dev->dev, "ME_CSR_HA", ME_CSR_HA, reg); + + return reg; } /** @@ -98,7 +105,12 @@ static inline u32 mei_me_mecsr_read(const struct mei_device *dev) */ static inline u32 mei_hcsr_read(const struct mei_device *dev) { - return mei_me_reg_read(to_me_hw(dev), H_CSR); + u32 reg; + + reg = mei_me_reg_read(to_me_hw(dev), H_CSR); + trace_mei_reg_read(dev->dev, "H_CSR", H_CSR, reg); + + return reg; } /** @@ -109,6 +121,7 @@ static inline u32 mei_hcsr_read(const struct mei_device *dev) */ static inline void mei_hcsr_write(struct mei_device *dev, u32 reg) { + trace_mei_reg_write(dev->dev, "H_CSR", H_CSR, reg); mei_me_reg_write(to_me_hw(dev), H_CSR, reg); } @@ -555,9 +568,14 @@ static int mei_me_read_slots(struct mei_device *dev, unsigned char *buffer, static void mei_me_pg_enter(struct mei_device *dev) { struct mei_me_hw *hw = to_me_hw(dev); - u32 reg = mei_me_reg_read(hw, H_HPG_CSR); + u32 reg; + + reg = mei_me_reg_read(hw, H_HPG_CSR); + trace_mei_reg_read(dev->dev, "H_HPG_CSR", H_HPG_CSR, reg); reg |= H_HPG_CSR_PGI; + + trace_mei_reg_write(dev->dev, "H_HPG_CSR", H_HPG_CSR, reg); mei_me_reg_write(hw, H_HPG_CSR, reg); } @@ -569,11 +587,16 @@ static void mei_me_pg_enter(struct mei_device *dev) static void mei_me_pg_exit(struct mei_device *dev) { struct mei_me_hw *hw = to_me_hw(dev); - u32 reg = mei_me_reg_read(hw, H_HPG_CSR); + u32 reg; + + reg = mei_me_reg_read(hw, H_HPG_CSR); + trace_mei_reg_read(dev->dev, "H_HPG_CSR", H_HPG_CSR, reg); WARN(!(reg & H_HPG_CSR_PGI), "PGI is not set\n"); reg |= H_HPG_CSR_PGIHEXR; + + trace_mei_reg_write(dev->dev, "H_HPG_CSR", H_HPG_CSR, reg); mei_me_reg_write(hw, H_HPG_CSR, reg); } diff --git a/drivers/misc/mei/mei-trace.c b/drivers/misc/mei/mei-trace.c new file mode 100644 index 0000000..388efb5 --- /dev/null +++ b/drivers/misc/mei/mei-trace.c @@ -0,0 +1,25 @@ +/* + * + * Intel Management Engine Interface (Intel MEI) Linux driver + * Copyright (c) 2015, 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. + * + */ +#include + +/* sparse doesn't like tracepoint macros */ +#ifndef __CHECKER__ +#define CREATE_TRACE_POINTS +#include "mei-trace.h" + +EXPORT_TRACEPOINT_SYMBOL(mei_reg_read); +EXPORT_TRACEPOINT_SYMBOL(mei_reg_write); +#endif /* __CHECKER__ */ diff --git a/drivers/misc/mei/mei-trace.h b/drivers/misc/mei/mei-trace.h new file mode 100644 index 0000000..d5c38d1 --- /dev/null +++ b/drivers/misc/mei/mei-trace.h @@ -0,0 +1,74 @@ +/* + * + * Intel Management Engine Interface (Intel MEI) Linux driver + * Copyright (c) 2015, 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. + * + */ + +#if !defined(_MEI_TRACE_H_) || defined(TRACE_HEADER_MULTI_READ) +#define _MEI_TRACE_H_ + +#include +#include +#include + +#undef TRACE_SYSTEM + +#define TRACE_SYSTEM mei +#define TRACE_SYSTEM_STRING __stringify(TRACE_SYSTEM) + +TRACE_EVENT(mei_reg_read, + TP_PROTO(const struct device *dev, const char *reg, u32 offs, u32 val), + TP_ARGS(dev, reg, offs, val), + TP_STRUCT__entry( + __string(dev, dev_name(dev)) + __field(const char *, reg) + __field(u32, offs) + __field(u32, val) + ), + TP_fast_assign( + __assign_str(dev, dev_name(dev)) + __entry->reg = reg; + __entry->offs = offs; + __entry->val = val; + ), + TP_printk("[%s] read %s:[%#x] = %#x", + __get_str(dev), __entry->reg, __entry->offs, __entry->val) +); + +TRACE_EVENT(mei_reg_write, + TP_PROTO(const struct device *dev, const char *reg, u32 offs, u32 val), + TP_ARGS(dev, reg, offs, val), + TP_STRUCT__entry( + __string(dev, dev_name(dev)) + __field(const char *, reg) + __field(u32, offs) + __field(u32, val) + ), + TP_fast_assign( + __assign_str(dev, dev_name(dev)) + __entry->reg = reg; + __entry->offs = offs; + __entry->val = val; + ), + TP_printk("[%s] write %s[%#x] = %#x)", + __get_str(dev), __entry->reg, __entry->offs, __entry->val) +); + +#endif /* _MEI_TRACE_H_ */ + +/* This part must be outside protection */ +#undef TRACE_INCLUDE_PATH +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_PATH . +#define TRACE_INCLUDE_FILE mei-trace +#include -- cgit v0.10.2 From 2d1995fce3f9b9a0bdb88d47144509e3b84db0f9 Mon Sep 17 00:00:00 2001 From: Alexander Usyskin Date: Tue, 10 Feb 2015 10:39:34 +0200 Subject: mei: me: change power gating function name conventions The current power gating naming was confusing, we wish to swap meanings of register and flow level power gating terms, For registers writing level use terms set and unset: mei_me_pg_set, mei_me_pg_unset For flow/high level use power gating enter and power gating exit terms mei_me_pg_enter_sync, mei_me_pg_exit_sync Signed-off-by: Alexander Usyskin Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/misc/mei/hw-me.c b/drivers/misc/mei/hw-me.c index d3aef82..6fb75e6 100644 --- a/drivers/misc/mei/hw-me.c +++ b/drivers/misc/mei/hw-me.c @@ -561,11 +561,11 @@ static int mei_me_read_slots(struct mei_device *dev, unsigned char *buffer, } /** - * mei_me_pg_enter - write pg enter register + * mei_me_pg_set - write pg enter register * * @dev: the device structure */ -static void mei_me_pg_enter(struct mei_device *dev) +static void mei_me_pg_set(struct mei_device *dev) { struct mei_me_hw *hw = to_me_hw(dev); u32 reg; @@ -580,11 +580,11 @@ static void mei_me_pg_enter(struct mei_device *dev) } /** - * mei_me_pg_exit - write pg exit register + * mei_me_pg_unset - write pg exit register * * @dev: the device structure */ -static void mei_me_pg_exit(struct mei_device *dev) +static void mei_me_pg_unset(struct mei_device *dev) { struct mei_me_hw *hw = to_me_hw(dev); u32 reg; @@ -601,13 +601,13 @@ static void mei_me_pg_exit(struct mei_device *dev) } /** - * mei_me_pg_set_sync - perform pg entry procedure + * mei_me_pg_enter_sync - perform pg entry procedure * * @dev: the device structure * * Return: 0 on success an error code otherwise */ -int mei_me_pg_set_sync(struct mei_device *dev) +int mei_me_pg_enter_sync(struct mei_device *dev) { struct mei_me_hw *hw = to_me_hw(dev); unsigned long timeout = mei_secs_to_jiffies(MEI_PGI_TIMEOUT); @@ -625,7 +625,7 @@ int mei_me_pg_set_sync(struct mei_device *dev) mutex_lock(&dev->device_lock); if (dev->pg_event == MEI_PG_EVENT_RECEIVED) { - mei_me_pg_enter(dev); + mei_me_pg_set(dev); ret = 0; } else { ret = -ETIME; @@ -638,13 +638,13 @@ int mei_me_pg_set_sync(struct mei_device *dev) } /** - * mei_me_pg_unset_sync - perform pg exit procedure + * mei_me_pg_exit_sync - perform pg exit procedure * * @dev: the device structure * * Return: 0 on success an error code otherwise */ -int mei_me_pg_unset_sync(struct mei_device *dev) +int mei_me_pg_exit_sync(struct mei_device *dev) { struct mei_me_hw *hw = to_me_hw(dev); unsigned long timeout = mei_secs_to_jiffies(MEI_PGI_TIMEOUT); @@ -655,7 +655,7 @@ int mei_me_pg_unset_sync(struct mei_device *dev) dev->pg_event = MEI_PG_EVENT_WAIT; - mei_me_pg_exit(dev); + mei_me_pg_unset(dev); mutex_unlock(&dev->device_lock); wait_event_timeout(dev->wait_pg, diff --git a/drivers/misc/mei/hw-me.h b/drivers/misc/mei/hw-me.h index d6567af..6022d52 100644 --- a/drivers/misc/mei/hw-me.h +++ b/drivers/misc/mei/hw-me.h @@ -71,8 +71,8 @@ extern const struct mei_cfg mei_me_pch8_sps_cfg; struct mei_device *mei_me_dev_init(struct pci_dev *pdev, const struct mei_cfg *cfg); -int mei_me_pg_set_sync(struct mei_device *dev); -int mei_me_pg_unset_sync(struct mei_device *dev); +int mei_me_pg_enter_sync(struct mei_device *dev); +int mei_me_pg_exit_sync(struct mei_device *dev); irqreturn_t mei_me_irq_quick_handler(int irq, void *dev_id); irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id); diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c index bd3039a..72fb381 100644 --- a/drivers/misc/mei/pci-me.c +++ b/drivers/misc/mei/pci-me.c @@ -389,7 +389,7 @@ static int mei_me_pm_runtime_suspend(struct device *device) mutex_lock(&dev->device_lock); if (mei_write_is_idle(dev)) - ret = mei_me_pg_set_sync(dev); + ret = mei_me_pg_enter_sync(dev); else ret = -EAGAIN; @@ -414,7 +414,7 @@ static int mei_me_pm_runtime_resume(struct device *device) mutex_lock(&dev->device_lock); - ret = mei_me_pg_unset_sync(dev); + ret = mei_me_pg_exit_sync(dev); mutex_unlock(&dev->device_lock); -- cgit v0.10.2 From 3908be6f9aa5517bc717f8ffdaaafd89a1b78471 Mon Sep 17 00:00:00 2001 From: Alexander Usyskin Date: Tue, 10 Feb 2015 10:39:35 +0200 Subject: mei: fix function names and format in KDoc Align functions names in KDoc with real ones. Fix comment format to be KDoc and fix wrong syntax there. Signed-off-by: Alexander Usyskin Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c index 48813c2..3e9cf0d 100644 --- a/drivers/misc/mei/client.c +++ b/drivers/misc/mei/client.c @@ -321,7 +321,7 @@ static inline bool mei_cl_cmp_id(const struct mei_cl *cl1, } /** - * mei_io_list_flush - removes cbs belonging to cl. + * __mei_io_list_flush - removes and frees cbs belonging to cl. * * @list: an instance of our list structure * @cl: host client, can be NULL for flushing the whole list @@ -540,10 +540,11 @@ struct mei_cl_cb *mei_cl_find_read_cb(struct mei_cl *cl) return NULL; } -/** mei_cl_link: allocate host id in the host map +/** + * mei_cl_link - allocate host id in the host map * - * @cl - host client - * @id - fixed host id or -1 for generic one + * @cl: host client + * @id: fixed host id or -1 for generic one * * Return: 0 on success * -EINVAL on incorrect values diff --git a/drivers/misc/mei/hw-txe.c b/drivers/misc/mei/hw-txe.c index 618ea72..7abafe7 100644 --- a/drivers/misc/mei/hw-txe.c +++ b/drivers/misc/mei/hw-txe.c @@ -412,7 +412,7 @@ static void mei_txe_intr_disable(struct mei_device *dev) mei_txe_br_reg_write(hw, HIER_REG, 0); } /** - * mei_txe_intr_disable - enable all interrupts + * mei_txe_intr_enable - enable all interrupts * * @dev: the device structure */ diff --git a/drivers/misc/mei/pci-txe.c b/drivers/misc/mei/pci-txe.c index c86e2dd..dcfcba4 100644 --- a/drivers/misc/mei/pci-txe.c +++ b/drivers/misc/mei/pci-txe.c @@ -63,7 +63,7 @@ static void mei_txe_pci_iounmap(struct pci_dev *pdev, struct mei_txe_hw *hw) } } /** - * mei_probe - Device Initialization Routine + * mei_txe_probe - Device Initialization Routine * * @pdev: PCI device structure * @ent: entry in mei_txe_pci_tbl @@ -193,7 +193,7 @@ end: } /** - * mei_remove - Device Removal Routine + * mei_txe_remove - Device Removal Routine * * @pdev: PCI device structure * diff --git a/drivers/misc/mei/wd.c b/drivers/misc/mei/wd.c index 475f1de..ac1e6235 100644 --- a/drivers/misc/mei/wd.c +++ b/drivers/misc/mei/wd.c @@ -202,10 +202,10 @@ err: return ret; } -/* +/** * mei_wd_ops_start - wd start command from the watchdog core. * - * @wd_dev - watchdog device struct + * @wd_dev: watchdog device struct * * Return: 0 if success, negative errno code for failure */ @@ -239,10 +239,10 @@ end_unlock: return err; } -/* +/** * mei_wd_ops_stop - wd stop command from the watchdog core. * - * @wd_dev - watchdog device struct + * @wd_dev: watchdog device struct * * Return: 0 if success, negative errno code for failure */ @@ -261,10 +261,10 @@ static int mei_wd_ops_stop(struct watchdog_device *wd_dev) return 0; } -/* +/** * mei_wd_ops_ping - wd ping command from the watchdog core. * - * @wd_dev - watchdog device struct + * @wd_dev: watchdog device struct * * Return: 0 if success, negative errno code for failure */ @@ -311,11 +311,11 @@ end: return ret; } -/* +/** * mei_wd_ops_set_timeout - wd set timeout command from the watchdog core. * - * @wd_dev - watchdog device struct - * @timeout - timeout value to set + * @wd_dev: watchdog device struct + * @timeout: timeout value to set * * Return: 0 if success, negative errno code for failure */ -- cgit v0.10.2 From 3d33ff2457355a9dd3c3178b04ab6669882b306c Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 10 Feb 2015 10:39:36 +0200 Subject: mei: fix device reset on mei_cl_irq_read_msg allocation failure On memory allocation failure mei_cl_irq_read_msg will return with error that will cause device reset. Instead we should propagate error to caller and just clean the read queues. Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/misc/mei/bus.c b/drivers/misc/mei/bus.c index be767f4..025626f 100644 --- a/drivers/misc/mei/bus.c +++ b/drivers/misc/mei/bus.c @@ -322,10 +322,16 @@ ssize_t __mei_cl_recv(struct mei_cl *cl, u8 *buf, size_t length) goto out; } + if (cb->status) { + rets = cb->status; + goto free; + } + r_length = min_t(size_t, length, cb->buf_idx); memcpy(buf, cb->response_buffer.data, r_length); rets = r_length; +free: mei_io_cb_free(cb); cl->reading_state = MEI_IDLE; cl->read_cb = NULL; diff --git a/drivers/misc/mei/interrupt.c b/drivers/misc/mei/interrupt.c index 711cddf..587cb04 100644 --- a/drivers/misc/mei/interrupt.c +++ b/drivers/misc/mei/interrupt.c @@ -69,85 +69,91 @@ static inline int mei_cl_hbm_equal(struct mei_cl *cl, cl->me_client_id == mei_hdr->me_addr; } /** - * mei_cl_is_reading - checks if the client - * is the one to read this message + * mei_cl_is_reading - checks if the client is in reading state * * @cl: mei client - * @mei_hdr: header of mei message * - * Return: true on match and false otherwise + * Return: true if the client is reading */ -static bool mei_cl_is_reading(struct mei_cl *cl, struct mei_msg_hdr *mei_hdr) +static bool mei_cl_is_reading(struct mei_cl *cl) { - return mei_cl_hbm_equal(cl, mei_hdr) && - cl->state == MEI_FILE_CONNECTED && + return cl->state == MEI_FILE_CONNECTED && cl->reading_state != MEI_READ_COMPLETE; } /** * mei_cl_irq_read_msg - process client message * - * @dev: the device structure + * @cl: reading client * @mei_hdr: header of mei client message - * @complete_list: An instance of our list structure + * @complete_list: completion list * - * Return: 0 on success, <0 on failure. + * Return: always 0 */ -static int mei_cl_irq_read_msg(struct mei_device *dev, +static int mei_cl_irq_read_msg(struct mei_cl *cl, struct mei_msg_hdr *mei_hdr, struct mei_cl_cb *complete_list) { - struct mei_cl *cl; - struct mei_cl_cb *cb, *next; + struct mei_device *dev = cl->dev; + struct mei_cl_cb *cb; unsigned char *buffer = NULL; - list_for_each_entry_safe(cb, next, &dev->read_list.list, list) { - cl = cb->cl; - if (!mei_cl_is_reading(cl, mei_hdr)) - continue; + list_for_each_entry(cb, &dev->read_list.list, list) { + if (cl == cb->cl) + break; + } - cl->reading_state = MEI_READING; + if (&cb->list == &dev->read_list.list) { + dev_err(dev->dev, "no reader found\n"); + goto out; + } - if (cb->response_buffer.size == 0 || - cb->response_buffer.data == NULL) { - cl_err(dev, cl, "response buffer is not allocated.\n"); - list_del(&cb->list); - return -ENOMEM; - } + if (!mei_cl_is_reading(cl)) { + cl_err(dev, cl, "cl is not reading state=%d reading state=%d\n", + cl->state, cl->reading_state); + goto out; + } - if (cb->response_buffer.size < mei_hdr->length + cb->buf_idx) { - cl_dbg(dev, cl, "message overflow. size %d len %d idx %ld\n", - cb->response_buffer.size, - mei_hdr->length, cb->buf_idx); - buffer = krealloc(cb->response_buffer.data, - mei_hdr->length + cb->buf_idx, - GFP_KERNEL); - - if (!buffer) { - list_del(&cb->list); - return -ENOMEM; - } - cb->response_buffer.data = buffer; - cb->response_buffer.size = - mei_hdr->length + cb->buf_idx; - } + cl->reading_state = MEI_READING; - buffer = cb->response_buffer.data + cb->buf_idx; - mei_read_slots(dev, buffer, mei_hdr->length); + if (cb->response_buffer.size == 0 || + cb->response_buffer.data == NULL) { + cl_err(dev, cl, "response buffer is not allocated.\n"); + list_move_tail(&cb->list, &complete_list->list); + cb->status = -ENOMEM; + goto out; + } - cb->buf_idx += mei_hdr->length; - if (mei_hdr->msg_complete) { - cl->status = 0; - list_del(&cb->list); - cl_dbg(dev, cl, "completed read length = %lu\n", - cb->buf_idx); - list_add_tail(&cb->list, &complete_list->list); + if (cb->response_buffer.size < mei_hdr->length + cb->buf_idx) { + cl_dbg(dev, cl, "message overflow. size %d len %d idx %ld\n", + cb->response_buffer.size, mei_hdr->length, cb->buf_idx); + buffer = krealloc(cb->response_buffer.data, + mei_hdr->length + cb->buf_idx, + GFP_KERNEL); + + if (!buffer) { + cb->status = -ENOMEM; + list_move_tail(&cb->list, &complete_list->list); + goto out; } - break; + cb->response_buffer.data = buffer; + cb->response_buffer.size = mei_hdr->length + cb->buf_idx; } - dev_dbg(dev->dev, "message read\n"); + buffer = cb->response_buffer.data + cb->buf_idx; + mei_read_slots(dev, buffer, mei_hdr->length); + + cb->buf_idx += mei_hdr->length; + if (mei_hdr->msg_complete) { + cl_dbg(dev, cl, "completed read length = %lu\n", + cb->buf_idx); + list_move_tail(&cb->list, &complete_list->list); + } + +out: if (!buffer) { + /* assume that mei_hdr->length <= MEI_RD_MSG_BUF_SIZE */ + BUG_ON(mei_hdr->length > MEI_RD_MSG_BUF_SIZE); mei_read_slots(dev, dev->rd_msg_buf, mei_hdr->length); dev_dbg(dev->dev, "discarding message " MEI_HDR_FMT "\n", MEI_HDR_PRM(mei_hdr)); @@ -389,14 +395,10 @@ int mei_irq_read_handler(struct mei_device *dev, goto end; } } else { - ret = mei_cl_irq_read_msg(dev, mei_hdr, cmpl_list); - if (ret) { - dev_err(dev->dev, "mei_cl_irq_read_msg failed = %d\n", - ret); - goto end; - } + ret = mei_cl_irq_read_msg(cl, mei_hdr, cmpl_list); } + reset_slots: /* reset the number of slots and header */ *slots = mei_count_full_read_slots(dev); @@ -636,4 +638,3 @@ out: schedule_delayed_work(&dev->timer_work, 2 * HZ); mutex_unlock(&dev->device_lock); } - diff --git a/drivers/misc/mei/main.c b/drivers/misc/mei/main.c index 3c019c0..cbdbf4a 100644 --- a/drivers/misc/mei/main.c +++ b/drivers/misc/mei/main.c @@ -192,8 +192,8 @@ static ssize_t mei_read(struct file *file, char __user *ubuf, goto out; } - if (cl->read_cb) { - cb = cl->read_cb; + cb = cl->read_cb; + if (cb) { /* read what left */ if (cb->buf_idx > *offset) goto copy_buffer; @@ -218,7 +218,8 @@ static ssize_t mei_read(struct file *file, char __user *ubuf, } if (MEI_READ_COMPLETE != cl->reading_state && - !waitqueue_active(&cl->rx_wait)) { + !waitqueue_active(&cl->rx_wait)) { + if (file->f_flags & O_NONBLOCK) { rets = -EAGAIN; goto out; @@ -248,12 +249,20 @@ static ssize_t mei_read(struct file *file, char __user *ubuf, rets = -ENODEV; goto out; } + if (cl->reading_state != MEI_READ_COMPLETE) { rets = 0; goto out; } - /* now copy the data to user space */ + copy_buffer: + /* now copy the data to user space */ + if (cb->status) { + rets = cb->status; + dev_dbg(dev->dev, "read operation failed %d\n", rets); + goto free; + } + dev_dbg(dev->dev, "buf.size = %d buf.idx= %ld\n", cb->response_buffer.size, cb->buf_idx); if (length == 0 || ubuf == NULL || *offset > cb->buf_idx) { diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index 102cc66..195e426 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -199,6 +199,7 @@ struct mei_cl; * @buf_idx: last read index * @read_time: last read operation time stamp (iamthif) * @file_object: pointer to file structure + * @status: io status of the cb * @internal: communication between driver and FW flag */ struct mei_cl_cb { @@ -210,6 +211,7 @@ struct mei_cl_cb { unsigned long buf_idx; unsigned long read_time; struct file *file_object; + int status; u32 internal:1; }; -- cgit v0.10.2 From db4756fd2f16efae8469dd1e37710919a0af9370 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 10 Feb 2015 10:39:37 +0200 Subject: mei: iamthif: fix device reset on mei_amthif_irq_read_msg On failure mei_amthif_irq_read_msg returns an error that will cause device reset but the issue is software one so instead we should propagate error to caller and just clean the read queues. As a side effect also removes useless BUG_ONs Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/misc/mei/amthif.c b/drivers/misc/mei/amthif.c index c4cb9a9..2ad2f94 100644 --- a/drivers/misc/mei/amthif.c +++ b/drivers/misc/mei/amthif.c @@ -195,23 +195,26 @@ int mei_amthif_read(struct mei_device *dev, struct file *file, dev_dbg(dev->dev, "woke up from sleep\n"); } + if (cb->status) { + rets = cb->status; + dev_dbg(dev->dev, "read operation failed %d\n", rets); + goto free; + } dev_dbg(dev->dev, "Got amthif data\n"); dev->iamthif_timer = 0; - if (cb) { - 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(&cb->list); - rets = -ETIME; - goto free; - } + 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(&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)) @@ -501,25 +504,42 @@ int mei_amthif_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb, * mei_amthif_irq_read_msg - read routine after ISR to * handle the read amthif message * - * @dev: the device structure + * @cl: mei client * @mei_hdr: header of amthif message - * @complete_list: An instance of our list structure + * @complete_list: completed callbacks list * - * Return: 0 on success, <0 on failure. + * Return: -ENODEV if cb is NULL 0 otherwise; error message is in cb->status */ -int mei_amthif_irq_read_msg(struct mei_device *dev, +int mei_amthif_irq_read_msg(struct mei_cl *cl, struct mei_msg_hdr *mei_hdr, struct mei_cl_cb *complete_list) { + struct mei_device *dev; struct mei_cl_cb *cb; unsigned char *buffer; + int ret = 0; - BUG_ON(mei_hdr->me_addr != dev->iamthif_cl.me_client_id); - BUG_ON(dev->iamthif_state != MEI_IAMTHIF_READING); + dev = cl->dev; - buffer = dev->iamthif_msg_buf + dev->iamthif_msg_buf_index; - BUG_ON(dev->iamthif_mtu < dev->iamthif_msg_buf_index + mei_hdr->length); + if (cl->state != MEI_FILE_CONNECTED) + goto err; + + if (dev->iamthif_state != MEI_IAMTHIF_READING) + goto err; + + cb = dev->iamthif_current_cb; + if (!cb) { + ret = -ENODEV; + goto err; + } + + if (dev->iamthif_mtu < dev->iamthif_msg_buf_index + mei_hdr->length) { + cb->status = -ERANGE; + goto err; + } + + buffer = dev->iamthif_msg_buf + dev->iamthif_msg_buf_index; mei_read_slots(dev, buffer, mei_hdr->length); dev->iamthif_msg_buf_index += mei_hdr->length; @@ -527,14 +547,8 @@ int mei_amthif_irq_read_msg(struct mei_device *dev, if (!mei_hdr->msg_complete) return 0; - dev_dbg(dev->dev, "amthif_message_buffer_index =%d\n", - mei_hdr->length); - dev_dbg(dev->dev, "completed amthif read.\n "); - if (!dev->iamthif_current_cb) - return -ENODEV; - cb = dev->iamthif_current_cb; dev->iamthif_current_cb = NULL; dev->iamthif_stall_timer = 0; @@ -543,10 +557,16 @@ int mei_amthif_irq_read_msg(struct mei_device *dev, if (dev->iamthif_ioctl) { /* found the iamthif cb */ dev_dbg(dev->dev, "complete the amthif read cb.\n "); - dev_dbg(dev->dev, "add the amthif read cb to complete.\n "); list_add_tail(&cb->list, &complete_list->list); } + return 0; + +err: + mei_read_slots(dev, dev->rd_msg_buf, mei_hdr->length); + dev_dbg(dev->dev, "discarding message " MEI_HDR_FMT "\n", + MEI_HDR_PRM(mei_hdr)); + return ret; } /** diff --git a/drivers/misc/mei/interrupt.c b/drivers/misc/mei/interrupt.c index 587cb04..151f0c8 100644 --- a/drivers/misc/mei/interrupt.c +++ b/drivers/misc/mei/interrupt.c @@ -384,11 +384,8 @@ int mei_irq_read_handler(struct mei_device *dev, goto end; } - if (mei_hdr->host_addr == dev->iamthif_cl.host_client_id && - MEI_FILE_CONNECTED == dev->iamthif_cl.state && - dev->iamthif_state == MEI_IAMTHIF_READING) { - - ret = mei_amthif_irq_read_msg(dev, mei_hdr, cmpl_list); + if (cl == &dev->iamthif_cl) { + ret = mei_amthif_irq_read_msg(cl, mei_hdr, cmpl_list); if (ret) { dev_err(dev->dev, "mei_amthif_irq_read_msg failed = %d\n", ret); diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index 195e426..b74d156 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -685,7 +685,7 @@ int mei_amthif_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb, struct mei_cl_cb *cmpl_list); void mei_amthif_complete(struct mei_device *dev, struct mei_cl_cb *cb); -int mei_amthif_irq_read_msg(struct mei_device *dev, +int mei_amthif_irq_read_msg(struct mei_cl *cl, struct mei_msg_hdr *mei_hdr, struct mei_cl_cb *complete_list); int mei_amthif_irq_read(struct mei_device *dev, s32 *slots); -- cgit v0.10.2 From 4e097bc5d5c948ff8a60ac38a5826f5b6699603d Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 10 Feb 2015 10:39:38 +0200 Subject: mei: iamthif: remove useless iamthif_ioctl variable iamthif_ioctl is obsolete and can be safely dropped Currently it is set to true during driver runtime Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/misc/mei/amthif.c b/drivers/misc/mei/amthif.c index 2ad2f94..788b00e 100644 --- a/drivers/misc/mei/amthif.c +++ b/drivers/misc/mei/amthif.c @@ -52,7 +52,6 @@ void mei_amthif_reset_params(struct mei_device *dev) dev->iamthif_msg_buf_size = 0; dev->iamthif_msg_buf_index = 0; dev->iamthif_canceled = false; - dev->iamthif_ioctl = false; dev->iamthif_state = MEI_IAMTHIF_IDLE; dev->iamthif_timer = 0; dev->iamthif_stall_timer = 0; @@ -279,7 +278,6 @@ static int mei_amthif_send_cmd(struct mei_device *dev, struct mei_cl_cb *cb) dev->iamthif_current_cb = cb; dev->iamthif_file_object = cb->file_object; dev->iamthif_canceled = false; - dev->iamthif_ioctl = true; dev->iamthif_msg_buf_size = cb->request_buffer.size; memcpy(dev->iamthif_msg_buf, cb->request_buffer.data, cb->request_buffer.size); @@ -375,7 +373,6 @@ void mei_amthif_run_next_cmd(struct mei_device *dev) dev->iamthif_msg_buf_size = 0; dev->iamthif_msg_buf_index = 0; dev->iamthif_canceled = false; - dev->iamthif_ioctl = true; dev->iamthif_state = MEI_IAMTHIF_IDLE; dev->iamthif_timer = 0; dev->iamthif_file_object = NULL; @@ -554,11 +551,9 @@ int mei_amthif_irq_read_msg(struct mei_cl *cl, dev->iamthif_stall_timer = 0; cb->buf_idx = dev->iamthif_msg_buf_index; cb->read_time = jiffies; - if (dev->iamthif_ioctl) { - /* found the iamthif cb */ - dev_dbg(dev->dev, "complete the amthif read cb.\n "); - list_add_tail(&cb->list, &complete_list->list); - } + + dev_dbg(dev->dev, "complete the amthif read cb.\n "); + list_add_tail(&cb->list, &complete_list->list); return 0; diff --git a/drivers/misc/mei/interrupt.c b/drivers/misc/mei/interrupt.c index 151f0c8..4cb602f 100644 --- a/drivers/misc/mei/interrupt.c +++ b/drivers/misc/mei/interrupt.c @@ -589,7 +589,6 @@ void mei_timer(struct work_struct *work) dev->iamthif_msg_buf_size = 0; dev->iamthif_msg_buf_index = 0; dev->iamthif_canceled = false; - dev->iamthif_ioctl = true; dev->iamthif_state = MEI_IAMTHIF_IDLE; dev->iamthif_timer = 0; diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index b74d156..6cc68de 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -488,7 +488,6 @@ const char *mei_pg_state_str(enum mei_pg_state state); * @iamthif_msg_buf_index : current index in amthif message request buffer * @iamthif_state : amthif processor state * @iamthif_flow_control_pending: amthif waits for flow control - * @iamthif_ioctl : wait for completion if amthif control message * @iamthif_canceled : current amthif command is canceled * * @init_work : work item for the device init @@ -588,7 +587,6 @@ struct mei_device { u32 iamthif_msg_buf_index; enum iamthif_states iamthif_state; bool iamthif_flow_control_pending; - bool iamthif_ioctl; bool iamthif_canceled; struct work_struct init_work; -- cgit v0.10.2 From c54bf3ab7536e062b507b625bfd2befb9b2cb841 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 10 Feb 2015 10:39:39 +0200 Subject: mei: iamthif: send flow control as a regular client Reuse common client mechanism for sending flow control hbm message. Add new function mei_amthif_read_start similar to mei_cl_read_start that puts control flow request onto the control write queue and drop mei_amthif_irq_read function Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/misc/mei/amthif.c b/drivers/misc/mei/amthif.c index 788b00e..e6f7180 100644 --- a/drivers/misc/mei/amthif.c +++ b/drivers/misc/mei/amthif.c @@ -255,6 +255,47 @@ out: } /** + * mei_amthif_read_start - queue message for sending read credential + * + * @cl: host client + * @file: file pointer of message recipient + * + * Return: 0 on success, <0 on failure. + */ +static int mei_amthif_read_start(struct mei_cl *cl, struct file *file) +{ + struct mei_device *dev = cl->dev; + struct mei_cl_cb *cb; + size_t length = dev->iamthif_mtu; + int rets; + + cb = mei_io_cb_init(cl, file); + if (!cb) { + rets = -ENOMEM; + goto err; + } + + rets = mei_io_cb_alloc_resp_buf(cb, length); + if (rets) + goto err; + + cb->fop_type = MEI_FOP_READ; + list_add_tail(&cb->list, &dev->ctrl_wr_list.list); + + dev->iamthif_msg_buf_index = 0; + dev->iamthif_msg_buf_size = 0; + + dev->iamthif_state = MEI_IAMTHIF_READING; + dev->iamthif_file_object = cb->file_object; + dev->iamthif_current_cb = cb; + + return 0; +err: + mei_io_cb_free(cb); + return rets; +} + +/** * mei_amthif_send_cmd - send amthif command to the ME * * @dev: the device structure @@ -287,6 +328,7 @@ static int mei_amthif_send_cmd(struct mei_device *dev, struct mei_cl_cb *cb) if (ret < 0) return ret; + cb->fop_type = MEI_FOP_WRITE; if (ret && mei_hbuf_acquire(dev)) { ret = 0; if (cb->request_buffer.size > mei_hbuf_max_len(dev)) { @@ -309,19 +351,17 @@ static int mei_amthif_send_cmd(struct mei_device *dev, struct mei_cl_cb *cb) if (mei_hdr.msg_complete) { if (mei_cl_flow_ctrl_reduce(cl)) return -EIO; - dev->iamthif_flow_control_pending = true; - dev->iamthif_state = MEI_IAMTHIF_FLOW_CONTROL; - dev_dbg(dev->dev, "add amthif cb to write waiting list\n"); - dev->iamthif_current_cb = cb; - dev->iamthif_file_object = cb->file_object; + cb->status = mei_amthif_read_start(cl, cb->file_object); list_add_tail(&cb->list, &dev->write_waiting_list.list); } else { dev_dbg(dev->dev, "message does not complete, so add amthif cb to write list.\n"); list_add_tail(&cb->list, &dev->write_list.list); } } else { + list_add_tail(&cb->list, &dev->write_list.list); } + return 0; } @@ -336,17 +376,10 @@ static int mei_amthif_send_cmd(struct mei_device *dev, struct mei_cl_cb *cb) */ int mei_amthif_write(struct mei_device *dev, struct mei_cl_cb *cb) { - int ret; - if (!dev || !cb) return -ENODEV; - ret = mei_io_cb_alloc_resp_buf(cb, dev->iamthif_mtu); - if (ret) - return ret; - cb->fop_type = MEI_FOP_WRITE; - if (!list_empty(&dev->amthif_cmd_list.list) || dev->iamthif_state != MEI_IAMTHIF_IDLE) { dev_dbg(dev->dev, @@ -383,7 +416,7 @@ void mei_amthif_run_next_cmd(struct mei_device *dev) typeof(*cb), list); if (!cb) return; - list_del(&cb->list); + list_del_init(&cb->list); ret = mei_amthif_send_cmd(dev, cb); if (ret) dev_warn(dev->dev, "amthif write failed status = %d\n", ret); @@ -483,13 +516,7 @@ int mei_amthif_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb, cl->status = 0; if (mei_hdr.msg_complete) { - dev->iamthif_state = MEI_IAMTHIF_FLOW_CONTROL; - dev->iamthif_flow_control_pending = true; - - /* save iamthif cb sent to amthif client */ - cb->buf_idx = dev->iamthif_msg_buf_index; - dev->iamthif_current_cb = cb; - + cb->status = mei_amthif_read_start(cl, cb->file_object); list_move_tail(&cb->list, &dev->write_waiting_list.list); } @@ -505,7 +532,7 @@ int mei_amthif_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb, * @mei_hdr: header of amthif message * @complete_list: completed callbacks list * - * Return: -ENODEV if cb is NULL 0 otherwise; error message is in cb->status + * Return: Always 0; error message is in cb->status */ int mei_amthif_irq_read_msg(struct mei_cl *cl, struct mei_msg_hdr *mei_hdr, @@ -514,7 +541,6 @@ int mei_amthif_irq_read_msg(struct mei_cl *cl, struct mei_device *dev; struct mei_cl_cb *cb; unsigned char *buffer; - int ret = 0; dev = cl->dev; @@ -524,10 +550,13 @@ int mei_amthif_irq_read_msg(struct mei_cl *cl, if (dev->iamthif_state != MEI_IAMTHIF_READING) goto err; - cb = dev->iamthif_current_cb; + list_for_each_entry(cb, &dev->read_list.list, list) { + if (cl == cb->cl) + break; + } - if (!cb) { - ret = -ENODEV; + if (&cb->list == &dev->read_list.list) { + dev_err(dev->dev, "no reader found\n"); goto err; } @@ -553,7 +582,7 @@ int mei_amthif_irq_read_msg(struct mei_cl *cl, cb->read_time = jiffies; dev_dbg(dev->dev, "complete the amthif read cb.\n "); - list_add_tail(&cb->list, &complete_list->list); + list_move_tail(&cb->list, &complete_list->list); return 0; @@ -561,38 +590,6 @@ err: mei_read_slots(dev, dev->rd_msg_buf, mei_hdr->length); dev_dbg(dev->dev, "discarding message " MEI_HDR_FMT "\n", MEI_HDR_PRM(mei_hdr)); - return ret; -} - -/** - * mei_amthif_irq_read - prepares to read amthif data. - * - * @dev: the device structure. - * @slots: free slots. - * - * Return: 0, OK; otherwise, error. - */ -int mei_amthif_irq_read(struct mei_device *dev, s32 *slots) -{ - u32 msg_slots = mei_data2slots(sizeof(struct hbm_flow_control)); - - if (*slots < msg_slots) - return -EMSGSIZE; - - *slots -= msg_slots; - - if (mei_hbm_cl_flow_control_req(dev, &dev->iamthif_cl)) { - dev_dbg(dev->dev, "iamthif flow control failed\n"); - return -EIO; - } - - dev_dbg(dev->dev, "iamthif flow control success\n"); - dev->iamthif_state = MEI_IAMTHIF_READING; - dev->iamthif_flow_control_pending = false; - dev->iamthif_msg_buf_index = 0; - dev->iamthif_msg_buf_size = 0; - dev->iamthif_stall_timer = MEI_IAMTHIF_STALL_TIMER; - dev->hbuf_is_ready = mei_hbuf_is_ready(dev); return 0; } @@ -604,17 +601,32 @@ int mei_amthif_irq_read(struct mei_device *dev, s32 *slots) */ void mei_amthif_complete(struct mei_device *dev, struct mei_cl_cb *cb) { + + if (cb->fop_type == MEI_FOP_WRITE) { + if (!cb->status) { + dev->iamthif_stall_timer = MEI_IAMTHIF_STALL_TIMER; + mei_io_cb_free(cb); + return; + } + /* + * 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); + return; + } + if (dev->iamthif_canceled != 1) { dev->iamthif_state = MEI_IAMTHIF_READ_COMPLETE; dev->iamthif_stall_timer = 0; memcpy(cb->response_buffer.data, - dev->iamthif_msg_buf, - dev->iamthif_msg_buf_index); + dev->iamthif_msg_buf, dev->iamthif_msg_buf_index); list_add_tail(&cb->list, &dev->amthif_rd_complete_list.list); dev_dbg(dev->dev, "amthif read completed\n"); dev->iamthif_timer = jiffies; dev_dbg(dev->dev, "dev->iamthif_timer = %ld\n", - dev->iamthif_timer); + dev->iamthif_timer); } else { mei_amthif_run_next_cmd(dev); } diff --git a/drivers/misc/mei/interrupt.c b/drivers/misc/mei/interrupt.c index 4cb602f..89f2fbc 100644 --- a/drivers/misc/mei/interrupt.c +++ b/drivers/misc/mei/interrupt.c @@ -43,7 +43,7 @@ void mei_irq_compl_handler(struct mei_device *dev, struct mei_cl_cb *compl_list) list_for_each_entry_safe(cb, next, &compl_list->list, list) { cl = cb->cl; - list_del(&cb->list); + list_del_init(&cb->list); dev_dbg(dev->dev, "completing call back.\n"); if (cl == &dev->iamthif_cl) @@ -386,11 +386,6 @@ int mei_irq_read_handler(struct mei_device *dev, if (cl == &dev->iamthif_cl) { ret = mei_amthif_irq_read_msg(cl, mei_hdr, cmpl_list); - if (ret) { - dev_err(dev->dev, "mei_amthif_irq_read_msg failed = %d\n", - ret); - goto end; - } } else { ret = mei_cl_irq_read_msg(cl, mei_hdr, cmpl_list); } @@ -448,21 +443,9 @@ int mei_irq_write_handler(struct mei_device *dev, struct mei_cl_cb *cmpl_list) cl = cb->cl; cl->status = 0; - list_del(&cb->list); - if (cb->fop_type == MEI_FOP_WRITE && - cl != &dev->iamthif_cl) { - cl_dbg(dev, cl, "MEI WRITE COMPLETE\n"); - cl->writing_state = MEI_WRITE_COMPLETE; - list_add_tail(&cb->list, &cmpl_list->list); - } - if (cl == &dev->iamthif_cl) { - cl_dbg(dev, cl, "check iamthif flow control.\n"); - if (dev->iamthif_flow_control_pending) { - ret = mei_amthif_irq_read(dev, &slots); - if (ret) - return ret; - } - } + cl_dbg(dev, cl, "MEI WRITE COMPLETE\n"); + cl->writing_state = MEI_WRITE_COMPLETE; + list_move_tail(&cb->list, &cmpl_list->list); } if (dev->wd_state == MEI_WD_STOPPING) { diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index 6cc68de..fc460af1 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -487,7 +487,6 @@ const char *mei_pg_state_str(enum mei_pg_state state); * @iamthif_msg_buf_size : size of current amthif message request buffer * @iamthif_msg_buf_index : current index in amthif message request buffer * @iamthif_state : amthif processor state - * @iamthif_flow_control_pending: amthif waits for flow control * @iamthif_canceled : current amthif command is canceled * * @init_work : work item for the device init @@ -586,7 +585,6 @@ struct mei_device { u32 iamthif_msg_buf_size; u32 iamthif_msg_buf_index; enum iamthif_states iamthif_state; - bool iamthif_flow_control_pending; bool iamthif_canceled; struct work_struct init_work; -- cgit v0.10.2 From 8660172e1d6528be02eba78516ff8282e694bb26 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 10 Feb 2015 10:39:40 +0200 Subject: mei: iamthif: use client write functions Reduce code duplication in amthif code by reusing regular client write functions. Add completed flag to cb so amthif client can add rx credits on write completion Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/misc/mei/amthif.c b/drivers/misc/mei/amthif.c index e6f7180..916625a 100644 --- a/drivers/misc/mei/amthif.c +++ b/drivers/misc/mei/amthif.c @@ -298,110 +298,47 @@ err: /** * mei_amthif_send_cmd - send amthif command to the ME * - * @dev: the device structure + * @cl: the host client * @cb: mei call back struct * * Return: 0 on success, <0 on failure. - * */ -static int mei_amthif_send_cmd(struct mei_device *dev, struct mei_cl_cb *cb) +static int mei_amthif_send_cmd(struct mei_cl *cl, struct mei_cl_cb *cb) { - struct mei_msg_hdr mei_hdr; - struct mei_cl *cl; + struct mei_device *dev; int ret; - if (!dev || !cb) + if (!cl->dev || !cb) return -ENODEV; - dev_dbg(dev->dev, "write data to amthif client.\n"); + dev = cl->dev; dev->iamthif_state = MEI_IAMTHIF_WRITING; dev->iamthif_current_cb = cb; dev->iamthif_file_object = cb->file_object; dev->iamthif_canceled = false; - dev->iamthif_msg_buf_size = cb->request_buffer.size; - memcpy(dev->iamthif_msg_buf, cb->request_buffer.data, - cb->request_buffer.size); - cl = &dev->iamthif_cl; - ret = mei_cl_flow_ctrl_creds(cl); + ret = mei_cl_write(cl, cb, false); if (ret < 0) return ret; - cb->fop_type = MEI_FOP_WRITE; - if (ret && mei_hbuf_acquire(dev)) { - ret = 0; - if (cb->request_buffer.size > mei_hbuf_max_len(dev)) { - mei_hdr.length = mei_hbuf_max_len(dev); - mei_hdr.msg_complete = 0; - } else { - mei_hdr.length = cb->request_buffer.size; - mei_hdr.msg_complete = 1; - } - - mei_hdr.host_addr = cl->host_client_id; - mei_hdr.me_addr = cl->me_client_id; - mei_hdr.reserved = 0; - mei_hdr.internal = 0; - dev->iamthif_msg_buf_index += mei_hdr.length; - ret = mei_write_message(dev, &mei_hdr, dev->iamthif_msg_buf); - if (ret) - return ret; - - if (mei_hdr.msg_complete) { - if (mei_cl_flow_ctrl_reduce(cl)) - return -EIO; - cb->status = mei_amthif_read_start(cl, cb->file_object); - list_add_tail(&cb->list, &dev->write_waiting_list.list); - } else { - dev_dbg(dev->dev, "message does not complete, so add amthif cb to write list.\n"); - list_add_tail(&cb->list, &dev->write_list.list); - } - } else { - - list_add_tail(&cb->list, &dev->write_list.list); - } + if (cb->completed) + cb->status = mei_amthif_read_start(cl, cb->file_object); return 0; } /** - * mei_amthif_write - write amthif data to amthif client + * mei_amthif_run_next_cmd - send next amt command from queue * * @dev: the device structure - * @cb: mei call back struct * * Return: 0 on success, <0 on failure. - * */ -int mei_amthif_write(struct mei_device *dev, struct mei_cl_cb *cb) -{ - if (!dev || !cb) - return -ENODEV; - - cb->fop_type = MEI_FOP_WRITE; - if (!list_empty(&dev->amthif_cmd_list.list) || - dev->iamthif_state != MEI_IAMTHIF_IDLE) { - dev_dbg(dev->dev, - "amthif state = %d\n", dev->iamthif_state); - dev_dbg(dev->dev, "AMTHIF: add cb to the wait list\n"); - list_add_tail(&cb->list, &dev->amthif_cmd_list.list); - return 0; - } - return mei_amthif_send_cmd(dev, cb); -} -/** - * mei_amthif_run_next_cmd - send next amt command from queue - * - * @dev: the device structure - */ -void mei_amthif_run_next_cmd(struct mei_device *dev) +int mei_amthif_run_next_cmd(struct mei_device *dev) { + struct mei_cl *cl = &dev->iamthif_cl; struct mei_cl_cb *cb; - int ret; - - if (!dev) - return; dev->iamthif_msg_buf_size = 0; dev->iamthif_msg_buf_index = 0; @@ -415,13 +352,37 @@ void mei_amthif_run_next_cmd(struct mei_device *dev) cb = list_first_entry_or_null(&dev->amthif_cmd_list.list, typeof(*cb), list); if (!cb) - return; + return 0; + list_del_init(&cb->list); - ret = mei_amthif_send_cmd(dev, cb); - if (ret) - dev_warn(dev->dev, "amthif write failed status = %d\n", ret); + return mei_amthif_send_cmd(cl, cb); } +/** + * mei_amthif_write - write amthif data to amthif client + * + * @cl: host client + * @cb: mei call back struct + * + * Return: 0 on success, <0 on failure. + */ +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; + + cb->fop_type = MEI_FOP_WRITE; + list_add_tail(&cb->list, &dev->amthif_cmd_list.list); + return mei_amthif_run_next_cmd(dev); +} unsigned int mei_amthif_poll(struct mei_device *dev, struct file *file, poll_table *wait) @@ -461,65 +422,14 @@ unsigned int mei_amthif_poll(struct mei_device *dev, int mei_amthif_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb, struct mei_cl_cb *cmpl_list) { - struct mei_device *dev = cl->dev; - struct mei_msg_hdr mei_hdr; - size_t len = dev->iamthif_msg_buf_size - dev->iamthif_msg_buf_index; - u32 msg_slots = mei_data2slots(len); - int slots; - int rets; - - rets = mei_cl_flow_ctrl_creds(cl); - if (rets < 0) - return rets; - - if (rets == 0) { - cl_dbg(dev, cl, "No flow control credentials: not sending.\n"); - return 0; - } - - mei_hdr.host_addr = cl->host_client_id; - mei_hdr.me_addr = cl->me_client_id; - mei_hdr.reserved = 0; - mei_hdr.internal = 0; - - slots = mei_hbuf_empty_slots(dev); - - if (slots >= msg_slots) { - mei_hdr.length = len; - mei_hdr.msg_complete = 1; - /* Split the message only if we can write the whole host buffer */ - } else if (slots == dev->hbuf_depth) { - msg_slots = slots; - len = (slots * sizeof(u32)) - sizeof(struct mei_msg_hdr); - mei_hdr.length = len; - mei_hdr.msg_complete = 0; - } else { - /* wait for next time the host buffer is empty */ - return 0; - } - - dev_dbg(dev->dev, MEI_HDR_FMT, MEI_HDR_PRM(&mei_hdr)); - - rets = mei_write_message(dev, &mei_hdr, - dev->iamthif_msg_buf + dev->iamthif_msg_buf_index); - if (rets) { - dev->iamthif_state = MEI_IAMTHIF_IDLE; - cl->status = rets; - list_del(&cb->list); - return rets; - } - - if (mei_cl_flow_ctrl_reduce(cl)) - return -EIO; + int ret; - dev->iamthif_msg_buf_index += mei_hdr.length; - cl->status = 0; + ret = mei_cl_irq_write(cl, cb, cmpl_list); + if (ret) + return ret; - if (mei_hdr.msg_complete) { + if (cb->completed) cb->status = mei_amthif_read_start(cl, cb->file_object); - list_move_tail(&cb->list, &dev->write_waiting_list.list); - } - return 0; } diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c index 3e9cf0d..d9f4e28 100644 --- a/drivers/misc/mei/client.c +++ b/drivers/misc/mei/client.c @@ -1106,6 +1106,7 @@ int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb, cl->status = 0; cl->writing_state = MEI_WRITING; cb->buf_idx += mei_hdr.length; + cb->completed = mei_hdr.msg_complete == 1; if (mei_hdr.msg_complete) { if (mei_cl_flow_ctrl_reduce(cl)) @@ -1194,6 +1195,7 @@ int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb, bool blocking) cl->writing_state = MEI_WRITING; cb->buf_idx = mei_hdr.length; + cb->completed = mei_hdr.msg_complete == 1; out: if (mei_hdr.msg_complete) { diff --git a/drivers/misc/mei/main.c b/drivers/misc/mei/main.c index cbdbf4a..9d1a8cb 100644 --- a/drivers/misc/mei/main.c +++ b/drivers/misc/mei/main.c @@ -401,7 +401,7 @@ static ssize_t mei_write(struct file *file, const char __user *ubuf, } if (cl == &dev->iamthif_cl) { - rets = mei_amthif_write(dev, write_cb); + rets = mei_amthif_write(cl, write_cb); if (rets) { dev_err(dev->dev, diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index fc460af1..2f2242f 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -201,6 +201,7 @@ struct mei_cl; * @file_object: pointer to file structure * @status: io status of the cb * @internal: communication between driver and FW flag + * @completed: the transfer or reception has completed */ struct mei_cl_cb { struct list_head list; @@ -213,6 +214,7 @@ struct mei_cl_cb { struct file *file_object; int status; u32 internal:1; + u32 completed:1; }; /** @@ -662,8 +664,6 @@ void mei_amthif_reset_params(struct mei_device *dev); int mei_amthif_host_init(struct mei_device *dev); -int mei_amthif_write(struct mei_device *dev, struct mei_cl_cb *priv_cb); - int mei_amthif_read(struct mei_device *dev, struct file *file, char __user *ubuf, size_t length, loff_t *offset); @@ -675,8 +675,8 @@ int mei_amthif_release(struct mei_device *dev, struct file *file); struct mei_cl_cb *mei_amthif_find_read_list_entry(struct mei_device *dev, struct file *file); -void mei_amthif_run_next_cmd(struct mei_device *dev); - +int mei_amthif_write(struct mei_cl *cl, struct mei_cl_cb *cb); +int mei_amthif_run_next_cmd(struct mei_device *dev); int mei_amthif_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb, struct mei_cl_cb *cmpl_list); -- cgit v0.10.2 From 331e4187017e5dc12fddfcca3f8041e5610ea23b Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 10 Feb 2015 10:39:41 +0200 Subject: mei: iamthif: use regular client read functions Reduce code duplication in amthif by reusing regular client read functions. The change also removes the need for amthif own buffering Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/misc/mei/amthif.c b/drivers/misc/mei/amthif.c index 916625a..4060e2f 100644 --- a/drivers/misc/mei/amthif.c +++ b/drivers/misc/mei/amthif.c @@ -49,8 +49,6 @@ void mei_amthif_reset_params(struct mei_device *dev) { /* reset iamthif parameters. */ dev->iamthif_current_cb = NULL; - dev->iamthif_msg_buf_size = 0; - dev->iamthif_msg_buf_index = 0; dev->iamthif_canceled = false; dev->iamthif_state = MEI_IAMTHIF_IDLE; dev->iamthif_timer = 0; @@ -69,7 +67,6 @@ int mei_amthif_host_init(struct mei_device *dev) { struct mei_cl *cl = &dev->iamthif_cl; struct mei_me_client *me_cl; - unsigned char *msg_buf; int ret; dev->iamthif_state = MEI_IAMTHIF_IDLE; @@ -90,18 +87,6 @@ int mei_amthif_host_init(struct mei_device *dev) dev->iamthif_mtu = me_cl->props.max_msg_length; dev_dbg(dev->dev, "IAMTHIF_MTU = %d\n", dev->iamthif_mtu); - kfree(dev->iamthif_msg_buf); - dev->iamthif_msg_buf = NULL; - - /* allocate storage for ME message buffer */ - msg_buf = kcalloc(dev->iamthif_mtu, - sizeof(unsigned char), GFP_KERNEL); - if (!msg_buf) { - ret = -ENOMEM; - goto out; - } - - dev->iamthif_msg_buf = msg_buf; ret = mei_cl_link(cl, MEI_IAMTHIF_HOST_CLIENT_ID); if (ret < 0) { @@ -282,9 +267,6 @@ static int mei_amthif_read_start(struct mei_cl *cl, struct file *file) cb->fop_type = MEI_FOP_READ; list_add_tail(&cb->list, &dev->ctrl_wr_list.list); - dev->iamthif_msg_buf_index = 0; - dev->iamthif_msg_buf_size = 0; - dev->iamthif_state = MEI_IAMTHIF_READING; dev->iamthif_file_object = cb->file_object; dev->iamthif_current_cb = cb; @@ -340,8 +322,6 @@ int mei_amthif_run_next_cmd(struct mei_device *dev) struct mei_cl *cl = &dev->iamthif_cl; struct mei_cl_cb *cb; - dev->iamthif_msg_buf_size = 0; - dev->iamthif_msg_buf_index = 0; dev->iamthif_canceled = false; dev->iamthif_state = MEI_IAMTHIF_IDLE; dev->iamthif_timer = 0; @@ -440,67 +420,34 @@ int mei_amthif_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb, * * @cl: mei client * @mei_hdr: header of amthif message - * @complete_list: completed callbacks list + * @cmpl_list: completed callbacks list * - * Return: Always 0; error message is in cb->status + * Return: -ENODEV if cb is NULL 0 otherwise; error message is in cb->status */ int mei_amthif_irq_read_msg(struct mei_cl *cl, struct mei_msg_hdr *mei_hdr, - struct mei_cl_cb *complete_list) + struct mei_cl_cb *cmpl_list) { struct mei_device *dev; - struct mei_cl_cb *cb; - unsigned char *buffer; + int ret; dev = cl->dev; - if (cl->state != MEI_FILE_CONNECTED) - goto err; - if (dev->iamthif_state != MEI_IAMTHIF_READING) - goto err; - - list_for_each_entry(cb, &dev->read_list.list, list) { - if (cl == cb->cl) - break; - } - - if (&cb->list == &dev->read_list.list) { - dev_err(dev->dev, "no reader found\n"); - goto err; - } - - if (dev->iamthif_mtu < dev->iamthif_msg_buf_index + mei_hdr->length) { - cb->status = -ERANGE; - goto err; - } - - buffer = dev->iamthif_msg_buf + dev->iamthif_msg_buf_index; - mei_read_slots(dev, buffer, mei_hdr->length); + return 0; - dev->iamthif_msg_buf_index += mei_hdr->length; + ret = mei_cl_irq_read_msg(cl, mei_hdr, cmpl_list); + if (ret) + return ret; if (!mei_hdr->msg_complete) return 0; dev_dbg(dev->dev, "completed amthif read.\n "); - dev->iamthif_current_cb = NULL; - dev->iamthif_stall_timer = 0; - cb->buf_idx = dev->iamthif_msg_buf_index; - cb->read_time = jiffies; - - dev_dbg(dev->dev, "complete the amthif read cb.\n "); - list_move_tail(&cb->list, &complete_list->list); return 0; - -err: - mei_read_slots(dev, dev->rd_msg_buf, mei_hdr->length); - dev_dbg(dev->dev, "discarding message " MEI_HDR_FMT "\n", - MEI_HDR_PRM(mei_hdr)); - return 0; } /** @@ -530,8 +477,6 @@ void mei_amthif_complete(struct mei_device *dev, struct mei_cl_cb *cb) if (dev->iamthif_canceled != 1) { dev->iamthif_state = MEI_IAMTHIF_READ_COMPLETE; dev->iamthif_stall_timer = 0; - memcpy(cb->response_buffer.data, - dev->iamthif_msg_buf, dev->iamthif_msg_buf_index); list_add_tail(&cb->list, &dev->amthif_rd_complete_list.list); dev_dbg(dev->dev, "amthif read completed\n"); dev->iamthif_timer = jiffies; diff --git a/drivers/misc/mei/client.h b/drivers/misc/mei/client.h index 80386f9..21cf626 100644 --- a/drivers/misc/mei/client.h +++ b/drivers/misc/mei/client.h @@ -102,6 +102,8 @@ bool mei_cl_is_other_connecting(struct mei_cl *cl); int mei_cl_disconnect(struct mei_cl *cl); int mei_cl_connect(struct mei_cl *cl, struct file *file); int mei_cl_read_start(struct mei_cl *cl, size_t length); +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); int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb, struct mei_cl_cb *cmpl_list); diff --git a/drivers/misc/mei/interrupt.c b/drivers/misc/mei/interrupt.c index 89f2fbc..466c1d2 100644 --- a/drivers/misc/mei/interrupt.c +++ b/drivers/misc/mei/interrupt.c @@ -82,6 +82,24 @@ static bool mei_cl_is_reading(struct mei_cl *cl) } /** + * mei_irq_discard_msg - discard received message + * + * @dev: mei device + * @hdr: message header + */ +static inline +void mei_irq_discard_msg(struct mei_device *dev, struct mei_msg_hdr *hdr) +{ + /* + * no need to check for size as it is guarantied + * that length fits into rd_msg_buf + */ + mei_read_slots(dev, dev->rd_msg_buf, hdr->length); + dev_dbg(dev->dev, "discarding message " MEI_HDR_FMT "\n", + MEI_HDR_PRM(hdr)); +} + +/** * mei_cl_irq_read_msg - process client message * * @cl: reading client @@ -90,9 +108,9 @@ static bool mei_cl_is_reading(struct mei_cl *cl) * * Return: always 0 */ -static int mei_cl_irq_read_msg(struct mei_cl *cl, - struct mei_msg_hdr *mei_hdr, - struct mei_cl_cb *complete_list) +int mei_cl_irq_read_msg(struct mei_cl *cl, + struct mei_msg_hdr *mei_hdr, + struct mei_cl_cb *complete_list) { struct mei_device *dev = cl->dev; struct mei_cl_cb *cb; @@ -144,20 +162,17 @@ static int mei_cl_irq_read_msg(struct mei_cl *cl, mei_read_slots(dev, buffer, mei_hdr->length); cb->buf_idx += mei_hdr->length; + if (mei_hdr->msg_complete) { + cb->read_time = jiffies; cl_dbg(dev, cl, "completed read length = %lu\n", cb->buf_idx); list_move_tail(&cb->list, &complete_list->list); } out: - if (!buffer) { - /* assume that mei_hdr->length <= MEI_RD_MSG_BUF_SIZE */ - BUG_ON(mei_hdr->length > MEI_RD_MSG_BUF_SIZE); - mei_read_slots(dev, dev->rd_msg_buf, mei_hdr->length); - dev_dbg(dev->dev, "discarding message " MEI_HDR_FMT "\n", - MEI_HDR_PRM(mei_hdr)); - } + if (!buffer) + mei_irq_discard_msg(dev, mei_hdr); return 0; } @@ -569,8 +584,6 @@ void mei_timer(struct work_struct *work) if (--dev->iamthif_stall_timer == 0) { dev_err(dev->dev, "timer: amthif hanged.\n"); mei_reset(dev); - dev->iamthif_msg_buf_size = 0; - dev->iamthif_msg_buf_index = 0; dev->iamthif_canceled = false; dev->iamthif_state = MEI_IAMTHIF_IDLE; dev->iamthif_timer = 0; diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index 2f2242f..57a47d6 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -485,9 +485,6 @@ const char *mei_pg_state_str(enum mei_pg_state state); * @iamthif_mtu : amthif client max message length * @iamthif_timer : time stamp of current amthif command completion * @iamthif_stall_timer : timer to detect amthif hang - * @iamthif_msg_buf : amthif current message buffer - * @iamthif_msg_buf_size : size of current amthif message request buffer - * @iamthif_msg_buf_index : current index in amthif message request buffer * @iamthif_state : amthif processor state * @iamthif_canceled : current amthif command is canceled * @@ -583,9 +580,6 @@ struct mei_device { int iamthif_mtu; unsigned long iamthif_timer; u32 iamthif_stall_timer; - unsigned char *iamthif_msg_buf; /* Note: memory has to be allocated */ - u32 iamthif_msg_buf_size; - u32 iamthif_msg_buf_index; enum iamthif_states iamthif_state; bool iamthif_canceled; -- cgit v0.10.2 From 5db7514d9333c920791538c850cfb9dbd19025f7 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 10 Feb 2015 10:39:42 +0200 Subject: mei: use only one buffer in callback The callback structure is used exclusively for reading or writing therefore there is no reason to hold both response and request buffers in the callback structure Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/misc/mei/amthif.c b/drivers/misc/mei/amthif.c index 4060e2f..2cc41cb 100644 --- a/drivers/misc/mei/amthif.c +++ b/drivers/misc/mei/amthif.c @@ -213,15 +213,15 @@ int mei_amthif_read(struct mei_device *dev, struct file *file, * remove message from deletion list */ - dev_dbg(dev->dev, "amthif cb->response_buffer size - %d\n", - cb->response_buffer.size); + 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); /* length is being truncated to PAGE_SIZE, however, * the buf_idx may point beyond */ length = min_t(size_t, length, (cb->buf_idx - *offset)); - if (copy_to_user(ubuf, cb->response_buffer.data + *offset, length)) { + if (copy_to_user(ubuf, cb->buf.data + *offset, length)) { dev_dbg(dev->dev, "failed to copy data to userland\n"); rets = -EFAULT; } else { @@ -260,7 +260,7 @@ static int mei_amthif_read_start(struct mei_cl *cl, struct file *file) goto err; } - rets = mei_io_cb_alloc_resp_buf(cb, length); + rets = mei_io_cb_alloc_buf(cb, length); if (rets) goto err; diff --git a/drivers/misc/mei/bus.c b/drivers/misc/mei/bus.c index 025626f..36b949a 100644 --- a/drivers/misc/mei/bus.c +++ b/drivers/misc/mei/bus.c @@ -261,11 +261,11 @@ static ssize_t ___mei_cl_send(struct mei_cl *cl, u8 *buf, size_t length, goto out; } - rets = mei_io_cb_alloc_req_buf(cb, length); + rets = mei_io_cb_alloc_buf(cb, length); if (rets < 0) goto out; - memcpy(cb->request_buffer.data, buf, length); + memcpy(cb->buf.data, buf, length); rets = mei_cl_write(cl, cb, blocking); @@ -328,7 +328,7 @@ ssize_t __mei_cl_recv(struct mei_cl *cl, u8 *buf, size_t length) } r_length = min_t(size_t, length, cb->buf_idx); - memcpy(buf, cb->response_buffer.data, r_length); + memcpy(buf, cb->buf.data, r_length); rets = r_length; free: diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c index d9f4e28..5ecb6cc 100644 --- a/drivers/misc/mei/client.c +++ b/drivers/misc/mei/client.c @@ -376,8 +376,7 @@ void mei_io_cb_free(struct mei_cl_cb *cb) if (cb == NULL) return; - kfree(cb->request_buffer.data); - kfree(cb->response_buffer.data); + kfree(cb->buf.data); kfree(cb); } @@ -406,7 +405,7 @@ struct mei_cl_cb *mei_io_cb_init(struct mei_cl *cl, struct file *fp) } /** - * mei_io_cb_alloc_req_buf - allocate request buffer + * mei_io_cb_alloc_buf - allocate callback buffer * * @cb: io callback structure * @length: size of the buffer @@ -415,7 +414,7 @@ struct mei_cl_cb *mei_io_cb_init(struct mei_cl *cl, struct file *fp) * -EINVAL if cb is NULL * -ENOMEM if allocation failed */ -int mei_io_cb_alloc_req_buf(struct mei_cl_cb *cb, size_t length) +int mei_io_cb_alloc_buf(struct mei_cl_cb *cb, size_t length) { if (!cb) return -EINVAL; @@ -423,38 +422,12 @@ int mei_io_cb_alloc_req_buf(struct mei_cl_cb *cb, size_t length) if (length == 0) return 0; - cb->request_buffer.data = kmalloc(length, GFP_KERNEL); - if (!cb->request_buffer.data) + cb->buf.data = kmalloc(length, GFP_KERNEL); + if (!cb->buf.data) return -ENOMEM; - cb->request_buffer.size = length; + cb->buf.size = length; return 0; } -/** - * mei_io_cb_alloc_resp_buf - allocate response buffer - * - * @cb: io callback structure - * @length: size of the buffer - * - * Return: 0 on success - * -EINVAL if cb is NULL - * -ENOMEM if allocation failed - */ -int mei_io_cb_alloc_resp_buf(struct mei_cl_cb *cb, size_t length) -{ - if (!cb) - return -EINVAL; - - if (length == 0) - return 0; - - cb->response_buffer.data = kmalloc(length, GFP_KERNEL); - if (!cb->response_buffer.data) - return -ENOMEM; - cb->response_buffer.size = length; - return 0; -} - - /** * mei_cl_flush_queues - flushes queue lists belonging to cl. @@ -1005,7 +978,7 @@ int mei_cl_read_start(struct mei_cl *cl, size_t length) goto out; } - rets = mei_io_cb_alloc_resp_buf(cb, length); + rets = mei_io_cb_alloc_buf(cb, length); if (rets) goto out; @@ -1059,7 +1032,7 @@ int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb, dev = cl->dev; - buf = &cb->request_buffer; + buf = &cb->buf; rets = mei_cl_flow_ctrl_creds(cl); if (rets < 0) @@ -1094,7 +1067,7 @@ int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb, } cl_dbg(dev, cl, "buf: size = %d idx = %lu\n", - cb->request_buffer.size, cb->buf_idx); + cb->buf.size, cb->buf_idx); rets = mei_write_message(dev, &mei_hdr, buf->data + cb->buf_idx); if (rets) { @@ -1144,7 +1117,7 @@ int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb, bool blocking) dev = cl->dev; - buf = &cb->request_buffer; + buf = &cb->buf; cl_dbg(dev, cl, "size=%d\n", buf->size); diff --git a/drivers/misc/mei/client.h b/drivers/misc/mei/client.h index 21cf626..d430a6e 100644 --- a/drivers/misc/mei/client.h +++ b/drivers/misc/mei/client.h @@ -49,8 +49,7 @@ void mei_me_cl_rm_all(struct mei_device *dev); */ struct mei_cl_cb *mei_io_cb_init(struct mei_cl *cl, struct file *fp); void mei_io_cb_free(struct mei_cl_cb *priv_cb); -int mei_io_cb_alloc_req_buf(struct mei_cl_cb *cb, size_t length); -int mei_io_cb_alloc_resp_buf(struct mei_cl_cb *cb, size_t length); +int mei_io_cb_alloc_buf(struct mei_cl_cb *cb, size_t length); /** diff --git a/drivers/misc/mei/interrupt.c b/drivers/misc/mei/interrupt.c index 466c1d2..60469a0 100644 --- a/drivers/misc/mei/interrupt.c +++ b/drivers/misc/mei/interrupt.c @@ -134,19 +134,17 @@ int mei_cl_irq_read_msg(struct mei_cl *cl, cl->reading_state = MEI_READING; - if (cb->response_buffer.size == 0 || - cb->response_buffer.data == NULL) { + if (cb->buf.size == 0 || cb->buf.data == NULL) { cl_err(dev, cl, "response buffer is not allocated.\n"); list_move_tail(&cb->list, &complete_list->list); cb->status = -ENOMEM; goto out; } - if (cb->response_buffer.size < mei_hdr->length + cb->buf_idx) { + if (cb->buf.size < mei_hdr->length + cb->buf_idx) { cl_dbg(dev, cl, "message overflow. size %d len %d idx %ld\n", - cb->response_buffer.size, mei_hdr->length, cb->buf_idx); - buffer = krealloc(cb->response_buffer.data, - mei_hdr->length + cb->buf_idx, + cb->buf.size, mei_hdr->length, cb->buf_idx); + buffer = krealloc(cb->buf.data, mei_hdr->length + cb->buf_idx, GFP_KERNEL); if (!buffer) { @@ -154,11 +152,11 @@ int mei_cl_irq_read_msg(struct mei_cl *cl, list_move_tail(&cb->list, &complete_list->list); goto out; } - cb->response_buffer.data = buffer; - cb->response_buffer.size = mei_hdr->length + cb->buf_idx; + cb->buf.data = buffer; + cb->buf.size = mei_hdr->length + cb->buf_idx; } - buffer = cb->response_buffer.data + cb->buf_idx; + buffer = cb->buf.data + cb->buf_idx; mei_read_slots(dev, buffer, mei_hdr->length); cb->buf_idx += mei_hdr->length; diff --git a/drivers/misc/mei/main.c b/drivers/misc/mei/main.c index 9d1a8cb..1d44d11 100644 --- a/drivers/misc/mei/main.c +++ b/drivers/misc/mei/main.c @@ -264,7 +264,7 @@ copy_buffer: } dev_dbg(dev->dev, "buf.size = %d buf.idx= %ld\n", - cb->response_buffer.size, cb->buf_idx); + cb->buf.size, cb->buf_idx); if (length == 0 || ubuf == NULL || *offset > cb->buf_idx) { rets = -EMSGSIZE; goto free; @@ -274,7 +274,7 @@ copy_buffer: * however buf_idx may point beyond that */ length = min_t(size_t, length, cb->buf_idx - *offset); - if (copy_to_user(ubuf, cb->response_buffer.data + *offset, length)) { + if (copy_to_user(ubuf, cb->buf.data + *offset, length)) { dev_dbg(dev->dev, "failed to copy data to userland\n"); rets = -EFAULT; goto free; @@ -389,11 +389,11 @@ static ssize_t mei_write(struct file *file, const char __user *ubuf, rets = -ENOMEM; goto out; } - rets = mei_io_cb_alloc_req_buf(write_cb, length); + rets = mei_io_cb_alloc_buf(write_cb, length); if (rets) goto out; - rets = copy_from_user(write_cb->request_buffer.data, ubuf, length); + rets = copy_from_user(write_cb->buf.data, ubuf, length); if (rets) { dev_dbg(dev->dev, "failed to copy data from userland\n"); rets = -EFAULT; diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index 57a47d6..1a0f6e9 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -194,8 +194,7 @@ struct mei_cl; * @list: link in callback queue * @cl: file client who is running this operation * @fop_type: file operation type - * @request_buffer: buffer to store request data - * @response_buffer: buffer to store response data + * @buf: buffer for data associated with the callback * @buf_idx: last read index * @read_time: last read operation time stamp (iamthif) * @file_object: pointer to file structure @@ -207,8 +206,7 @@ struct mei_cl_cb { struct list_head list; struct mei_cl *cl; enum mei_cb_file_ops fop_type; - struct mei_msg_data request_buffer; - struct mei_msg_data response_buffer; + struct mei_msg_data buf; unsigned long buf_idx; unsigned long read_time; struct file *file_object; -- cgit v0.10.2 From bca67d681c4864b74fa5fae9ee47e562d1e272b1 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 10 Feb 2015 10:39:43 +0200 Subject: mei: always initialize the callback with the intended operation type We set the operation type at initialization time as each cb is used only for a single type of operation As a byproduct we add a convenient wrapper for allocating cb with the data buffer. Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/misc/mei/amthif.c b/drivers/misc/mei/amthif.c index 2cc41cb..3fdd223 100644 --- a/drivers/misc/mei/amthif.c +++ b/drivers/misc/mei/amthif.c @@ -254,7 +254,7 @@ static int mei_amthif_read_start(struct mei_cl *cl, struct file *file) size_t length = dev->iamthif_mtu; int rets; - cb = mei_io_cb_init(cl, file); + cb = mei_io_cb_init(cl, MEI_FOP_READ, file); if (!cb) { rets = -ENOMEM; goto err; @@ -264,7 +264,6 @@ static int mei_amthif_read_start(struct mei_cl *cl, struct file *file) if (rets) goto err; - cb->fop_type = MEI_FOP_READ; list_add_tail(&cb->list, &dev->ctrl_wr_list.list); dev->iamthif_state = MEI_IAMTHIF_READING; @@ -359,7 +358,6 @@ int mei_amthif_write(struct mei_cl *cl, struct mei_cl_cb *cb) dev = cl->dev; - cb->fop_type = MEI_FOP_WRITE; list_add_tail(&cb->list, &dev->amthif_cmd_list.list); return mei_amthif_run_next_cmd(dev); } diff --git a/drivers/misc/mei/bus.c b/drivers/misc/mei/bus.c index 36b949a..3e6ffed 100644 --- a/drivers/misc/mei/bus.c +++ b/drivers/misc/mei/bus.c @@ -255,16 +255,12 @@ static ssize_t ___mei_cl_send(struct mei_cl *cl, u8 *buf, size_t length, goto out; } - cb = mei_io_cb_init(cl, NULL); + cb = mei_cl_alloc_cb(cl, length, MEI_FOP_WRITE, NULL); if (!cb) { rets = -ENOMEM; goto out; } - rets = mei_io_cb_alloc_buf(cb, length); - if (rets < 0) - goto out; - memcpy(cb->buf.data, buf, length); rets = mei_cl_write(cl, cb, blocking); @@ -293,7 +289,7 @@ ssize_t __mei_cl_recv(struct mei_cl *cl, u8 *buf, size_t length) mutex_lock(&dev->device_lock); if (!cl->read_cb) { - rets = mei_cl_read_start(cl, length); + rets = mei_cl_read_start(cl, length, NULL); if (rets < 0) goto out; } @@ -392,7 +388,7 @@ static void mei_bus_event_work(struct work_struct *work) device->events = 0; /* Prepare for the next read */ - mei_cl_read_start(device->cl, 0); + mei_cl_read_start(device->cl, 0, NULL); } int mei_cl_register_event_cb(struct mei_cl_device *device, @@ -406,7 +402,7 @@ int mei_cl_register_event_cb(struct mei_cl_device *device, device->event_context = context; INIT_WORK(&device->event_work, mei_bus_event_work); - mei_cl_read_start(device->cl, 0); + mei_cl_read_start(device->cl, 0, NULL); return 0; } @@ -448,7 +444,7 @@ int mei_cl_enable_device(struct mei_cl_device *device) mutex_unlock(&dev->device_lock); if (device->event_cb && !cl->read_cb) - mei_cl_read_start(device->cl, 0); + mei_cl_read_start(device->cl, 0, NULL); if (!device->ops || !device->ops->enable) return 0; diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c index 5ecb6cc..5746101 100644 --- a/drivers/misc/mei/client.c +++ b/drivers/misc/mei/client.c @@ -384,11 +384,13 @@ void mei_io_cb_free(struct mei_cl_cb *cb) * mei_io_cb_init - allocate and initialize io callback * * @cl: mei client + * @type: operation type * @fp: pointer to file structure * * Return: mei_cl_cb pointer or NULL; */ -struct mei_cl_cb *mei_io_cb_init(struct mei_cl *cl, struct file *fp) +struct mei_cl_cb *mei_io_cb_init(struct mei_cl *cl, enum mei_cb_file_ops type, + struct file *fp) { struct mei_cl_cb *cb; @@ -401,6 +403,7 @@ struct mei_cl_cb *mei_io_cb_init(struct mei_cl *cl, struct file *fp) cb->file_object = fp; cb->cl = cl; cb->buf_idx = 0; + cb->fop_type = type; return cb; } @@ -430,6 +433,33 @@ int mei_io_cb_alloc_buf(struct mei_cl_cb *cb, size_t length) } /** + * mei_cl_alloc_cb - a convenient wrapper for allocating read cb + * + * @cl: host client + * @length: size of the buffer + * @type: operation type + * @fp: associated file pointer (might be NULL) + * + * 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) +{ + struct mei_cl_cb *cb; + + cb = mei_io_cb_init(cl, type, fp); + if (!cb) + return NULL; + + if (mei_io_cb_alloc_buf(cb, length)) { + mei_io_cb_free(cb); + return NULL; + } + + return cb; +} + +/** * mei_cl_flush_queues - flushes queue lists belonging to cl. * * @cl: host client @@ -688,13 +718,10 @@ int mei_cl_disconnect(struct mei_cl *cl) return rets; } - cb = mei_io_cb_init(cl, NULL); - if (!cb) { - rets = -ENOMEM; + cb = mei_io_cb_init(cl, MEI_FOP_DISCONNECT, NULL); + rets = cb ? 0 : -ENOMEM; + if (rets) goto free; - } - - cb->fop_type = MEI_FOP_DISCONNECT; if (mei_hbuf_acquire(dev)) { if (mei_hbm_cl_disconnect_req(dev, cl)) { @@ -795,13 +822,10 @@ int mei_cl_connect(struct mei_cl *cl, struct file *file) return rets; } - cb = mei_io_cb_init(cl, file); - if (!cb) { - rets = -ENOMEM; + cb = mei_io_cb_init(cl, MEI_FOP_CONNECT, file); + rets = cb ? 0 : -ENOMEM; + if (rets) goto out; - } - - cb->fop_type = MEI_FOP_CONNECT; /* run hbuf acquire last so we don't have to undo */ if (!mei_cl_is_other_connecting(cl) && mei_hbuf_acquire(dev)) { @@ -934,10 +958,11 @@ out: * * @cl: host client * @length: number of bytes to read + * @fp: pointer to file structure * * Return: 0 on success, <0 on failure. */ -int mei_cl_read_start(struct mei_cl *cl, size_t length) +int mei_cl_read_start(struct mei_cl *cl, size_t length, struct file *fp) { struct mei_device *dev; struct mei_cl_cb *cb; @@ -972,17 +997,11 @@ int mei_cl_read_start(struct mei_cl *cl, size_t length) return rets; } - cb = mei_io_cb_init(cl, NULL); - if (!cb) { - rets = -ENOMEM; - goto out; - } - - rets = mei_io_cb_alloc_buf(cb, length); + cb = mei_cl_alloc_cb(cl, length, MEI_FOP_READ, fp); + rets = cb ? 0 : -ENOMEM; if (rets) goto out; - cb->fop_type = MEI_FOP_READ; if (mei_hbuf_acquire(dev)) { rets = mei_hbm_cl_flow_control_req(dev, cl); if (rets < 0) @@ -1128,7 +1147,6 @@ int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb, bool blocking) return rets; } - cb->fop_type = MEI_FOP_WRITE; cb->buf_idx = 0; cl->writing_state = MEI_IDLE; diff --git a/drivers/misc/mei/client.h b/drivers/misc/mei/client.h index d430a6e..f7d0285 100644 --- a/drivers/misc/mei/client.h +++ b/drivers/misc/mei/client.h @@ -47,7 +47,8 @@ void mei_me_cl_rm_all(struct mei_device *dev); /* * MEI IO Functions */ -struct mei_cl_cb *mei_io_cb_init(struct mei_cl *cl, struct file *fp); +struct mei_cl_cb *mei_io_cb_init(struct mei_cl *cl, enum mei_cb_file_ops type, + 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); @@ -77,6 +78,8 @@ int mei_cl_unlink(struct mei_cl *cl); int mei_cl_flush_queues(struct mei_cl *cl); struct mei_cl_cb *mei_cl_find_read_cb(struct mei_cl *cl); +struct mei_cl_cb *mei_cl_alloc_cb(struct mei_cl *cl, size_t length, + enum mei_cb_file_ops type, struct file *fp); int mei_cl_flow_ctrl_creds(struct mei_cl *cl); @@ -100,7 +103,7 @@ static inline bool mei_cl_is_transitioning(struct mei_cl *cl) bool mei_cl_is_other_connecting(struct mei_cl *cl); int mei_cl_disconnect(struct mei_cl *cl); int mei_cl_connect(struct mei_cl *cl, struct file *file); -int mei_cl_read_start(struct mei_cl *cl, size_t length); +int mei_cl_read_start(struct mei_cl *cl, size_t length, struct file *fp); int mei_cl_irq_read_msg(struct mei_cl *cl, struct mei_msg_hdr *hdr, struct mei_cl_cb *cmpl_list); int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb, bool blocking); diff --git a/drivers/misc/mei/hbm.c b/drivers/misc/mei/hbm.c index 4f83e9a..2c581dc 100644 --- a/drivers/misc/mei/hbm.c +++ b/drivers/misc/mei/hbm.c @@ -684,10 +684,9 @@ static int mei_hbm_fw_disconnect_req(struct mei_device *dev, cl->state = MEI_FILE_DISCONNECTED; cl->timer_count = 0; - cb = mei_io_cb_init(cl, NULL); + cb = mei_io_cb_init(cl, MEI_FOP_DISCONNECT_RSP, NULL); if (!cb) return -ENOMEM; - cb->fop_type = MEI_FOP_DISCONNECT_RSP; cl_dbg(dev, cl, "add disconnect response as first\n"); list_add(&cb->list, &dev->ctrl_wr_list.list); } diff --git a/drivers/misc/mei/main.c b/drivers/misc/mei/main.c index 1d44d11..369de0a 100644 --- a/drivers/misc/mei/main.c +++ b/drivers/misc/mei/main.c @@ -209,7 +209,7 @@ static ssize_t mei_read(struct file *file, char __user *ubuf, *offset = 0; } - err = mei_cl_read_start(cl, length); + err = mei_cl_read_start(cl, length, file); if (err && err != -EBUSY) { dev_dbg(dev->dev, "mei start read failure with status = %d\n", err); @@ -383,15 +383,11 @@ static ssize_t mei_write(struct file *file, const char __user *ubuf, } else if (cl->reading_state == MEI_IDLE) *offset = 0; - - write_cb = mei_io_cb_init(cl, file); + write_cb = mei_cl_alloc_cb(cl, length, MEI_FOP_WRITE, file); if (!write_cb) { rets = -ENOMEM; goto out; } - rets = mei_io_cb_alloc_buf(write_cb, length); - if (rets) - goto out; rets = copy_from_user(write_cb->buf.data, ubuf, length); if (rets) { -- cgit v0.10.2 From 03b8d3419fdfc02d1984a0db51c8b74426e12605 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 10 Feb 2015 10:39:44 +0200 Subject: mei: add mei_cl_alloc_linked function Add convenient wrapper mei_cl_alloc_linked to simplify error handling Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c index 5746101..e263c07 100644 --- a/drivers/misc/mei/client.c +++ b/drivers/misc/mei/client.c @@ -547,11 +547,11 @@ struct mei_cl_cb *mei_cl_find_read_cb(struct mei_cl *cl) * mei_cl_link - allocate host id in the host map * * @cl: host client - * @id: fixed host id or -1 for generic one + * @id: fixed host id or MEI_HOST_CLIENT_ID_ANY (-1) for generic one * * Return: 0 on success * -EINVAL on incorrect values - * -ENONET if client not found + * -EMFILE if open count exceeded. */ int mei_cl_link(struct mei_cl *cl, int id) { @@ -870,6 +870,37 @@ out: } /** + * 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 *cl; + int ret; + + cl = mei_cl_allocate(dev); + if (!cl) { + ret = -ENOMEM; + goto err; + } + + ret = mei_cl_link(cl, id); + if (ret) + goto err; + + return cl; +err: + kfree(cl); + return ERR_PTR(ret); +} + + + +/** * mei_cl_flow_ctrl_creds - checks flow_control credits for cl. * * @cl: private data of the file object diff --git a/drivers/misc/mei/client.h b/drivers/misc/mei/client.h index f7d0285..c3d0e20 100644 --- a/drivers/misc/mei/client.h +++ b/drivers/misc/mei/client.h @@ -75,6 +75,8 @@ 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_unlink(struct mei_cl *cl); +struct mei_cl *mei_cl_alloc_linked(struct mei_device *dev, int id); + int mei_cl_flush_queues(struct mei_cl *cl); struct mei_cl_cb *mei_cl_find_read_cb(struct mei_cl *cl); diff --git a/drivers/misc/mei/main.c b/drivers/misc/mei/main.c index 369de0a..10fc3a6 100644 --- a/drivers/misc/mei/main.c +++ b/drivers/misc/mei/main.c @@ -59,24 +59,18 @@ static int mei_open(struct inode *inode, struct file *file) mutex_lock(&dev->device_lock); - cl = NULL; - - err = -ENODEV; if (dev->dev_state != MEI_DEV_ENABLED) { dev_dbg(dev->dev, "dev_state != MEI_ENABLED dev_state = %s\n", mei_dev_state_str(dev->dev_state)); + err = -ENODEV; goto err_unlock; } - err = -ENOMEM; - cl = mei_cl_allocate(dev); - if (!cl) - goto err_unlock; - - /* open_handle_count check is handled in the mei_cl_link */ - err = mei_cl_link(cl, MEI_HOST_CLIENT_ID_ANY); - if (err) + cl = mei_cl_alloc_linked(dev, MEI_HOST_CLIENT_ID_ANY); + if (IS_ERR(cl)) { + err = PTR_ERR(cl); goto err_unlock; + } file->private_data = cl; @@ -86,7 +80,6 @@ static int mei_open(struct inode *inode, struct file *file) err_unlock: mutex_unlock(&dev->device_lock); - kfree(cl); return err; } diff --git a/drivers/misc/mei/nfc.c b/drivers/misc/mei/nfc.c index bb61a11..c3bcb63 100644 --- a/drivers/misc/mei/nfc.c +++ b/drivers/misc/mei/nfc.c @@ -482,8 +482,8 @@ err: int mei_nfc_host_init(struct mei_device *dev) { struct mei_nfc_dev *ndev; - struct mei_cl *cl_info, *cl = NULL; - struct mei_me_client *me_cl; + struct mei_cl *cl_info, *cl; + struct mei_me_client *me_cl = NULL; int ret; @@ -500,17 +500,6 @@ int mei_nfc_host_init(struct mei_device *dev) goto err; } - ndev->cl_info = mei_cl_allocate(dev); - ndev->cl = mei_cl_allocate(dev); - - cl = ndev->cl; - cl_info = ndev->cl_info; - - if (!cl || !cl_info) { - ret = -ENOMEM; - goto err; - } - /* check for valid client id */ me_cl = mei_me_cl_by_uuid(dev, &mei_nfc_info_guid); if (!me_cl) { @@ -519,17 +508,21 @@ int mei_nfc_host_init(struct mei_device *dev) goto err; } + cl_info = mei_cl_alloc_linked(dev, MEI_HOST_CLIENT_ID_ANY); + if (IS_ERR(cl_info)) { + ret = PTR_ERR(cl_info); + goto err; + } + cl_info->me_client_id = me_cl->client_id; cl_info->cl_uuid = me_cl->props.protocol_name; mei_me_cl_put(me_cl); - - ret = mei_cl_link(cl_info, MEI_HOST_CLIENT_ID_ANY); - if (ret) - goto err; - + me_cl = NULL; list_add_tail(&cl_info->device_link, &dev->device_list); + ndev->cl_info = cl_info; + /* check for valid client id */ me_cl = mei_me_cl_by_uuid(dev, &mei_nfc_guid); if (!me_cl) { @@ -538,16 +531,21 @@ int mei_nfc_host_init(struct mei_device *dev) goto err; } + cl = mei_cl_alloc_linked(dev, MEI_HOST_CLIENT_ID_ANY); + if (IS_ERR(cl)) { + ret = PTR_ERR(cl); + goto err; + } + cl->me_client_id = me_cl->client_id; cl->cl_uuid = me_cl->props.protocol_name; mei_me_cl_put(me_cl); - - ret = mei_cl_link(cl, MEI_HOST_CLIENT_ID_ANY); - if (ret) - goto err; + me_cl = NULL; list_add_tail(&cl->device_link, &dev->device_list); + ndev->cl = cl; + ndev->req_id = 1; INIT_WORK(&ndev->init_work, mei_nfc_init); @@ -557,6 +555,7 @@ int mei_nfc_host_init(struct mei_device *dev) return 0; err: + mei_me_cl_put(me_cl); mei_nfc_free(ndev); return ret; -- cgit v0.10.2 From 928fa6664b362aad70c16f04483414f60743e15e Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 10 Feb 2015 10:39:45 +0200 Subject: mei: simplify io callback disposal Simplify disposal of io callback by removing the callback implicitly from its lookup list inside mei_io_cb_free Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/misc/mei/amthif.c b/drivers/misc/mei/amthif.c index 3fdd223..7b6ed0b 100644 --- a/drivers/misc/mei/amthif.c +++ b/drivers/misc/mei/amthif.c @@ -196,16 +196,16 @@ int mei_amthif_read(struct mei_device *dev, struct file *file, if (time_after(jiffies, timeout)) { dev_dbg(dev->dev, "amthif Time out\n"); /* 15 sec for the message has expired */ - list_del(&cb->list); + 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(&cb->list); + list_del_init(&cb->list); else if (cb->buf_idx > 0 && cb->buf_idx <= *offset) { /* end of the message has been reached */ - list_del(&cb->list); + list_del_init(&cb->list); rets = 0; goto free; } @@ -504,26 +504,22 @@ void mei_amthif_complete(struct mei_device *dev, struct mei_cl_cb *cb) static bool mei_clear_list(struct mei_device *dev, const struct file *file, struct list_head *mei_cb_list) { - struct mei_cl_cb *cb_pos = NULL; - struct mei_cl_cb *cb_next = NULL; + struct mei_cl *cl = &dev->iamthif_cl; + struct mei_cl_cb *cb, *next; bool removed = false; /* list all list member */ - list_for_each_entry_safe(cb_pos, cb_next, mei_cb_list, list) { + list_for_each_entry_safe(cb, next, mei_cb_list, list) { /* check if list member associated with a file */ - if (file == cb_pos->file_object) { - /* remove member from the list */ - list_del(&cb_pos->list); + if (file == cb->file_object) { /* check if cb equal to current iamthif cb */ - if (dev->iamthif_current_cb == cb_pos) { + if (dev->iamthif_current_cb == cb) { dev->iamthif_current_cb = NULL; /* send flow control to iamthif client */ - mei_hbm_cl_flow_control_req(dev, - &dev->iamthif_cl); + mei_hbm_cl_flow_control_req(dev, cl); } /* free all allocated buffers */ - mei_io_cb_free(cb_pos); - cb_pos = NULL; + mei_io_cb_free(cb); removed = true; } } diff --git a/drivers/misc/mei/bus.c b/drivers/misc/mei/bus.c index 3e6ffed..b538537 100644 --- a/drivers/misc/mei/bus.c +++ b/drivers/misc/mei/bus.c @@ -311,13 +311,13 @@ ssize_t __mei_cl_recv(struct mei_cl *cl, u8 *buf, size_t length) mutex_lock(&dev->device_lock); } - cb = cl->read_cb; if (cl->reading_state != MEI_READ_COMPLETE) { rets = 0; goto out; } + cb = cl->read_cb; if (cb->status) { rets = cb->status; goto free; @@ -329,8 +329,8 @@ ssize_t __mei_cl_recv(struct mei_cl *cl, u8 *buf, size_t length) free: mei_io_cb_free(cb); - cl->reading_state = MEI_IDLE; cl->read_cb = NULL; + cl->reading_state = MEI_IDLE; out: mutex_unlock(&dev->device_lock); @@ -486,23 +486,7 @@ int mei_cl_disable_device(struct mei_cl_device *device) /* Flush queues and remove any pending read */ mei_cl_flush_queues(cl); - - if (cl->read_cb) { - struct mei_cl_cb *cb = NULL; - - cb = mei_cl_find_read_cb(cl); - /* Remove entry from read list */ - if (cb) - list_del(&cb->list); - - cb = cl->read_cb; - cl->read_cb = NULL; - - if (cb) { - mei_io_cb_free(cb); - cb = NULL; - } - } + mei_io_cb_free(cl->read_cb); device->event_cb = NULL; diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c index e263c07..624bf01 100644 --- a/drivers/misc/mei/client.c +++ b/drivers/misc/mei/client.c @@ -321,6 +321,47 @@ static inline bool mei_cl_cmp_id(const struct mei_cl *cl1, } /** + * mei_io_cb_free - free mei_cb_private related memory + * + * @cb: mei callback struct + */ +void mei_io_cb_free(struct mei_cl_cb *cb) +{ + if (cb == NULL) + return; + + list_del(&cb->list); + kfree(cb->buf.data); + kfree(cb); +} + +/** + * mei_io_cb_init - allocate and initialize io callback + * + * @cl: mei client + * @type: operation type + * @fp: pointer to file structure + * + * 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) +{ + struct mei_cl_cb *cb; + + cb = kzalloc(sizeof(struct mei_cl_cb), GFP_KERNEL); + if (!cb) + return NULL; + + INIT_LIST_HEAD(&cb->list); + cb->file_object = fp; + cb->cl = cl; + cb->buf_idx = 0; + cb->fop_type = type; + return cb; +} + +/** * __mei_io_list_flush - removes and frees cbs belonging to cl. * * @list: an instance of our list structure @@ -330,13 +371,12 @@ static inline bool mei_cl_cmp_id(const struct mei_cl *cl1, static void __mei_io_list_flush(struct mei_cl_cb *list, struct mei_cl *cl, bool free) { - struct mei_cl_cb *cb; - struct mei_cl_cb *next; + struct mei_cl_cb *cb, *next; /* enable removing everything if no cl is specified */ list_for_each_entry_safe(cb, next, &list->list, list) { if (!cl || mei_cl_cmp_id(cl, cb->cl)) { - list_del(&cb->list); + list_del_init(&cb->list); if (free) mei_io_cb_free(cb); } @@ -354,7 +394,6 @@ void mei_io_list_flush(struct mei_cl_cb *list, struct mei_cl *cl) __mei_io_list_flush(list, cl, false); } - /** * mei_io_list_free - removes cb belonging to cl and free them * @@ -367,47 +406,6 @@ static inline void mei_io_list_free(struct mei_cl_cb *list, struct mei_cl *cl) } /** - * mei_io_cb_free - free mei_cb_private related memory - * - * @cb: mei callback struct - */ -void mei_io_cb_free(struct mei_cl_cb *cb) -{ - if (cb == NULL) - return; - - kfree(cb->buf.data); - kfree(cb); -} - -/** - * mei_io_cb_init - allocate and initialize io callback - * - * @cl: mei client - * @type: operation type - * @fp: pointer to file structure - * - * 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) -{ - struct mei_cl_cb *cb; - - cb = kzalloc(sizeof(struct mei_cl_cb), GFP_KERNEL); - if (!cb) - return NULL; - - mei_io_list_init(cb); - - cb->file_object = fp; - cb->cl = cl; - cb->buf_idx = 0; - cb->fop_type = type; - return cb; -} - -/** * mei_io_cb_alloc_buf - allocate callback buffer * * @cb: io callback structure diff --git a/drivers/misc/mei/hbm.c b/drivers/misc/mei/hbm.c index 2c581dc..58da925 100644 --- a/drivers/misc/mei/hbm.c +++ b/drivers/misc/mei/hbm.c @@ -639,7 +639,7 @@ static void mei_hbm_cl_res(struct mei_device *dev, continue; if (mei_hbm_cl_addr_equal(cl, rs)) { - list_del(&cb->list); + list_del_init(&cb->list); break; } } diff --git a/drivers/misc/mei/interrupt.c b/drivers/misc/mei/interrupt.c index 60469a0..1e2f3c7 100644 --- a/drivers/misc/mei/interrupt.c +++ b/drivers/misc/mei/interrupt.c @@ -202,7 +202,6 @@ static int mei_cl_irq_disconnect_rsp(struct mei_cl *cl, struct mei_cl_cb *cb, cl->state = MEI_FILE_DISCONNECTED; cl->status = 0; - list_del(&cb->list); mei_io_cb_free(cb); return ret; @@ -320,7 +319,7 @@ static int mei_cl_irq_connect(struct mei_cl *cl, struct mei_cl_cb *cb, if (ret) { cl->status = ret; cb->buf_idx = 0; - list_del(&cb->list); + list_del_init(&cb->list); return ret; } diff --git a/drivers/misc/mei/main.c b/drivers/misc/mei/main.c index 10fc3a6..c34853b 100644 --- a/drivers/misc/mei/main.c +++ b/drivers/misc/mei/main.c @@ -94,7 +94,6 @@ err_unlock: static int mei_release(struct inode *inode, struct file *file) { struct mei_cl *cl = file->private_data; - struct mei_cl_cb *cb; struct mei_device *dev; int rets = 0; @@ -118,23 +117,11 @@ static int mei_release(struct inode *inode, struct file *file) mei_cl_unlink(cl); - - /* free read cb */ - cb = NULL; - if (cl->read_cb) { - cb = mei_cl_find_read_cb(cl); - /* Remove entry from read list */ - if (cb) - list_del(&cb->list); - - cb = cl->read_cb; - cl->read_cb = NULL; - } + mei_io_cb_free(cl->read_cb); + cl->read_cb = NULL; file->private_data = NULL; - mei_io_cb_free(cb); - kfree(cl); out: mutex_unlock(&dev->device_lock); @@ -156,7 +143,6 @@ static ssize_t mei_read(struct file *file, char __user *ubuf, size_t length, loff_t *offset) { struct mei_cl *cl = file->private_data; - struct mei_cl_cb *cb_pos = NULL; struct mei_cl_cb *cb = NULL; struct mei_device *dev; int rets; @@ -279,13 +265,10 @@ copy_buffer: goto out; free: - cb_pos = mei_cl_find_read_cb(cl); - /* Remove entry from read list */ - if (cb_pos) - list_del(&cb_pos->list); mei_io_cb_free(cb); - cl->reading_state = MEI_IDLE; cl->read_cb = NULL; + + cl->reading_state = MEI_IDLE; out: dev_dbg(dev->dev, "end mei read rets= %d\n", rets); mutex_unlock(&dev->device_lock); @@ -355,7 +338,6 @@ static ssize_t mei_write(struct file *file, const char __user *ubuf, if (time_after(jiffies, timeout) || cl->reading_state == MEI_READ_COMPLETE) { *offset = 0; - list_del(&write_cb->list); mei_io_cb_free(write_cb); write_cb = NULL; } @@ -367,11 +349,10 @@ static ssize_t mei_write(struct file *file, const char __user *ubuf, *offset = 0; write_cb = mei_cl_find_read_cb(cl); if (write_cb) { - list_del(&write_cb->list); mei_io_cb_free(write_cb); write_cb = NULL; - cl->reading_state = MEI_IDLE; cl->read_cb = NULL; + cl->reading_state = MEI_IDLE; } } else if (cl->reading_state == MEI_IDLE) *offset = 0; -- cgit v0.10.2 From a9bed61053af13c0768f82c9d1c8793515dd067c Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 10 Feb 2015 10:39:46 +0200 Subject: mei: allow read concurrency Replace clunky read state machine with read stack implemented as per client read list, this is important mostly for mei drivers with unsolicited reads Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/misc/mei/bus.c b/drivers/misc/mei/bus.c index b538537..17ca7e2 100644 --- a/drivers/misc/mei/bus.c +++ b/drivers/misc/mei/bus.c @@ -288,19 +288,20 @@ ssize_t __mei_cl_recv(struct mei_cl *cl, u8 *buf, size_t length) mutex_lock(&dev->device_lock); - if (!cl->read_cb) { - rets = mei_cl_read_start(cl, length, NULL); - if (rets < 0) - goto out; - } + cb = mei_cl_read_cb(cl, NULL); + if (cb) + goto copy; + + rets = mei_cl_read_start(cl, length, NULL); + if (rets && rets != -EBUSY) + goto out; - if (cl->reading_state != MEI_READ_COMPLETE && - !waitqueue_active(&cl->rx_wait)) { + if (list_empty(&cl->rd_completed) && !waitqueue_active(&cl->rx_wait)) { mutex_unlock(&dev->device_lock); if (wait_event_interruptible(cl->rx_wait, - cl->reading_state == MEI_READ_COMPLETE || + (!list_empty(&cl->rd_completed)) || mei_cl_is_transitioning(cl))) { if (signal_pending(current)) @@ -309,15 +310,20 @@ ssize_t __mei_cl_recv(struct mei_cl *cl, u8 *buf, size_t length) } mutex_lock(&dev->device_lock); - } + if (mei_cl_is_transitioning(cl)) { + rets = -EBUSY; + goto out; + } + } - if (cl->reading_state != MEI_READ_COMPLETE) { + cb = mei_cl_read_cb(cl, NULL); + if (!cb) { rets = 0; goto out; } - cb = cl->read_cb; +copy: if (cb->status) { rets = cb->status; goto free; @@ -329,9 +335,6 @@ ssize_t __mei_cl_recv(struct mei_cl *cl, u8 *buf, size_t length) free: mei_io_cb_free(cb); - cl->read_cb = NULL; - cl->reading_state = MEI_IDLE; - out: mutex_unlock(&dev->device_lock); @@ -443,7 +446,7 @@ int mei_cl_enable_device(struct mei_cl_device *device) mutex_unlock(&dev->device_lock); - if (device->event_cb && !cl->read_cb) + if (device->event_cb) mei_cl_read_start(device->cl, 0, NULL); if (!device->ops || !device->ops->enable) @@ -485,8 +488,7 @@ int mei_cl_disable_device(struct mei_cl_device *device) } /* Flush queues and remove any pending read */ - mei_cl_flush_queues(cl); - mei_io_cb_free(cl->read_cb); + mei_cl_flush_queues(cl, NULL); device->event_cb = NULL; diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c index 624bf01..98a5363 100644 --- a/drivers/misc/mei/client.c +++ b/drivers/misc/mei/client.c @@ -458,13 +458,55 @@ struct mei_cl_cb *mei_cl_alloc_cb(struct mei_cl *cl, size_t length, } /** + * mei_cl_read_cb - find this cl's callback in the read list + * for a specific file + * + * @cl: host client + * @fp: file pointer (matching cb file object), may be NULL + * + * Return: cb on success, NULL if cb is not found + */ +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) + return cb; + + return NULL; +} + +/** + * mei_cl_read_cb_flush - free client's read pending and completed cbs + * for a specific file + * + * @cl: host client + * @fp: file pointer (matching cb file object), may be NULL + */ +void mei_cl_read_cb_flush(const struct mei_cl *cl, const struct file *fp) +{ + struct mei_cl_cb *cb, *next; + + list_for_each_entry_safe(cb, next, &cl->rd_completed, list) + if (!fp || fp == cb->file_object) + mei_io_cb_free(cb); + + + list_for_each_entry_safe(cb, next, &cl->rd_pending, list) + if (!fp || fp == cb->file_object) + mei_io_cb_free(cb); +} + +/** * mei_cl_flush_queues - flushes queue lists belonging to cl. * * @cl: host client + * @fp: file pointer (matching cb file object), may be NULL * * Return: 0 on success, -EINVAL if cl or cl->dev is NULL. */ -int mei_cl_flush_queues(struct mei_cl *cl) +int mei_cl_flush_queues(struct mei_cl *cl, const struct file *fp) { struct mei_device *dev; @@ -474,13 +516,15 @@ int mei_cl_flush_queues(struct mei_cl *cl) dev = cl->dev; cl_dbg(dev, cl, "remove list entry belonging to cl\n"); - mei_io_list_flush(&cl->dev->read_list, cl); mei_io_list_free(&cl->dev->write_list, cl); mei_io_list_free(&cl->dev->write_waiting_list, cl); 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); + return 0; } @@ -497,9 +541,10 @@ void mei_cl_init(struct mei_cl *cl, struct mei_device *dev) init_waitqueue_head(&cl->wait); init_waitqueue_head(&cl->rx_wait); init_waitqueue_head(&cl->tx_wait); + INIT_LIST_HEAD(&cl->rd_completed); + INIT_LIST_HEAD(&cl->rd_pending); INIT_LIST_HEAD(&cl->link); INIT_LIST_HEAD(&cl->device_link); - cl->reading_state = MEI_IDLE; cl->writing_state = MEI_IDLE; cl->dev = dev; } @@ -524,24 +569,6 @@ struct mei_cl *mei_cl_allocate(struct mei_device *dev) } /** - * mei_cl_find_read_cb - find this cl's callback in the read list - * - * @cl: host client - * - * Return: cb on success, NULL on error - */ -struct mei_cl_cb *mei_cl_find_read_cb(struct mei_cl *cl) -{ - struct mei_device *dev = cl->dev; - struct mei_cl_cb *cb; - - list_for_each_entry(cb, &dev->read_list.list, list) - if (mei_cl_cmp_id(cl, cb->cl)) - return cb; - return NULL; -} - -/** * mei_cl_link - allocate host id in the host map * * @cl: host client @@ -1006,10 +1033,10 @@ int mei_cl_read_start(struct mei_cl *cl, size_t length, struct file *fp) if (!mei_cl_is_connected(cl)) return -ENODEV; - if (cl->read_cb) { - cl_dbg(dev, cl, "read is pending.\n"); + /* HW currently supports only one pending read */ + if (!list_empty(&cl->rd_pending)) return -EBUSY; - } + me_cl = mei_me_cl_by_uuid_id(dev, &cl->cl_uuid, cl->me_client_id); if (!me_cl) { cl_err(dev, cl, "no such me client %d\n", cl->me_client_id); @@ -1036,13 +1063,11 @@ int mei_cl_read_start(struct mei_cl *cl, size_t length, struct file *fp) if (rets < 0) goto out; - list_add_tail(&cb->list, &dev->read_list.list); + list_add_tail(&cb->list, &cl->rd_pending); } else { list_add_tail(&cb->list, &dev->ctrl_wr_list.list); } - cl->read_cb = cb; - out: cl_dbg(dev, cl, "rpm: autosuspend\n"); pm_runtime_mark_last_busy(dev->dev); @@ -1268,9 +1293,8 @@ void mei_cl_complete(struct mei_cl *cl, struct mei_cl_cb *cb) if (waitqueue_active(&cl->tx_wait)) wake_up_interruptible(&cl->tx_wait); - } else if (cb->fop_type == MEI_FOP_READ && - MEI_READING == cl->reading_state) { - cl->reading_state = MEI_READ_COMPLETE; + } else if (cb->fop_type == MEI_FOP_READ) { + list_add_tail(&cb->list, &cl->rd_completed); if (waitqueue_active(&cl->rx_wait)) wake_up_interruptible(&cl->rx_wait); else diff --git a/drivers/misc/mei/client.h b/drivers/misc/mei/client.h index c3d0e20..eb02f34 100644 --- a/drivers/misc/mei/client.h +++ b/drivers/misc/mei/client.h @@ -77,11 +77,12 @@ int mei_cl_unlink(struct mei_cl *cl); struct mei_cl *mei_cl_alloc_linked(struct mei_device *dev, int id); -int mei_cl_flush_queues(struct mei_cl *cl); -struct mei_cl_cb *mei_cl_find_read_cb(struct mei_cl *cl); - +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); +int mei_cl_flush_queues(struct mei_cl *cl, const struct file *fp); int mei_cl_flow_ctrl_creds(struct mei_cl *cl); diff --git a/drivers/misc/mei/debugfs.c b/drivers/misc/mei/debugfs.c index 50fc663..d9cd7e6e 100644 --- a/drivers/misc/mei/debugfs.c +++ b/drivers/misc/mei/debugfs.c @@ -117,7 +117,7 @@ static ssize_t mei_dbgfs_read_active(struct file *fp, char __user *ubuf, pos += scnprintf(buf + pos, bufsz - pos, "%2d|%2d|%4d|%5d|%2d|%2d|\n", i, cl->me_client_id, cl->host_client_id, cl->state, - cl->reading_state, cl->writing_state); + !list_empty(&cl->rd_completed), cl->writing_state); i++; } out: diff --git a/drivers/misc/mei/init.c b/drivers/misc/mei/init.c index 106c054..4596401 100644 --- a/drivers/misc/mei/init.c +++ b/drivers/misc/mei/init.c @@ -395,7 +395,6 @@ void mei_device_init(struct mei_device *dev, dev->dev_state = MEI_DEV_INITIALIZING; dev->reset_count = 0; - mei_io_list_init(&dev->read_list); mei_io_list_init(&dev->write_list); mei_io_list_init(&dev->write_waiting_list); mei_io_list_init(&dev->ctrl_wr_list); diff --git a/drivers/misc/mei/interrupt.c b/drivers/misc/mei/interrupt.c index 1e2f3c7..3f23629 100644 --- a/drivers/misc/mei/interrupt.c +++ b/drivers/misc/mei/interrupt.c @@ -68,18 +68,6 @@ static inline int mei_cl_hbm_equal(struct mei_cl *cl, return cl->host_client_id == mei_hdr->host_addr && cl->me_client_id == mei_hdr->me_addr; } -/** - * mei_cl_is_reading - checks if the client is in reading state - * - * @cl: mei client - * - * Return: true if the client is reading - */ -static bool mei_cl_is_reading(struct mei_cl *cl) -{ - return cl->state == MEI_FILE_CONNECTED && - cl->reading_state != MEI_READ_COMPLETE; -} /** * mei_irq_discard_msg - discard received message @@ -116,24 +104,18 @@ int mei_cl_irq_read_msg(struct mei_cl *cl, struct mei_cl_cb *cb; unsigned char *buffer = NULL; - list_for_each_entry(cb, &dev->read_list.list, list) { - if (cl == cb->cl) - break; - } - - if (&cb->list == &dev->read_list.list) { - dev_err(dev->dev, "no reader found\n"); + cb = list_first_entry_or_null(&cl->rd_pending, struct mei_cl_cb, list); + if (!cb) { + cl_err(dev, cl, "pending read cb not found\n"); goto out; } - if (!mei_cl_is_reading(cl)) { - cl_err(dev, cl, "cl is not reading state=%d reading state=%d\n", - cl->state, cl->reading_state); + if (cl->state != MEI_FILE_CONNECTED) { + cl_dbg(dev, cl, "not connected\n"); + cb->status = -ENODEV; goto out; } - cl->reading_state = MEI_READING; - if (cb->buf.size == 0 || cb->buf.data == NULL) { cl_err(dev, cl, "response buffer is not allocated.\n"); list_move_tail(&cb->list, &complete_list->list); @@ -163,8 +145,7 @@ int mei_cl_irq_read_msg(struct mei_cl *cl, if (mei_hdr->msg_complete) { cb->read_time = jiffies; - cl_dbg(dev, cl, "completed read length = %lu\n", - cb->buf_idx); + cl_dbg(dev, cl, "completed read length = %lu\n", cb->buf_idx); list_move_tail(&cb->list, &complete_list->list); } @@ -281,7 +262,7 @@ static int mei_cl_irq_read(struct mei_cl *cl, struct mei_cl_cb *cb, return ret; } - list_move_tail(&cb->list, &dev->read_list.list); + list_move_tail(&cb->list, &cl->rd_pending); return 0; } diff --git a/drivers/misc/mei/main.c b/drivers/misc/mei/main.c index c34853b..d80867e 100644 --- a/drivers/misc/mei/main.c +++ b/drivers/misc/mei/main.c @@ -112,14 +112,11 @@ static int mei_release(struct inode *inode, struct file *file) cl_dbg(dev, cl, "disconnecting\n"); rets = mei_cl_disconnect(cl); } - mei_cl_flush_queues(cl); + mei_cl_flush_queues(cl, file); cl_dbg(dev, cl, "removing\n"); mei_cl_unlink(cl); - mei_io_cb_free(cl->read_cb); - cl->read_cb = NULL; - file->private_data = NULL; kfree(cl); @@ -143,8 +140,8 @@ static ssize_t mei_read(struct file *file, char __user *ubuf, size_t length, loff_t *offset) { struct mei_cl *cl = file->private_data; - struct mei_cl_cb *cb = NULL; struct mei_device *dev; + struct mei_cl_cb *cb = NULL; int rets; int err; @@ -171,7 +168,7 @@ static ssize_t mei_read(struct file *file, char __user *ubuf, goto out; } - cb = cl->read_cb; + cb = mei_cl_read_cb(cl, file); if (cb) { /* read what left */ if (cb->buf_idx > *offset) @@ -196,9 +193,7 @@ static ssize_t mei_read(struct file *file, char __user *ubuf, goto out; } - if (MEI_READ_COMPLETE != cl->reading_state && - !waitqueue_active(&cl->rx_wait)) { - + if (list_empty(&cl->rd_completed) && !waitqueue_active(&cl->rx_wait)) { if (file->f_flags & O_NONBLOCK) { rets = -EAGAIN; goto out; @@ -207,7 +202,7 @@ static ssize_t mei_read(struct file *file, char __user *ubuf, mutex_unlock(&dev->device_lock); if (wait_event_interruptible(cl->rx_wait, - MEI_READ_COMPLETE == cl->reading_state || + (!list_empty(&cl->rd_completed)) || mei_cl_is_transitioning(cl))) { if (signal_pending(current)) @@ -222,14 +217,8 @@ static ssize_t mei_read(struct file *file, char __user *ubuf, } } - cb = cl->read_cb; - + cb = mei_cl_read_cb(cl, file); if (!cb) { - rets = -ENODEV; - goto out; - } - - if (cl->reading_state != MEI_READ_COMPLETE) { rets = 0; goto out; } @@ -266,9 +255,7 @@ copy_buffer: free: mei_io_cb_free(cb); - cl->read_cb = NULL; - cl->reading_state = MEI_IDLE; out: dev_dbg(dev->dev, "end mei read rets= %d\n", rets); mutex_unlock(&dev->device_lock); @@ -335,8 +322,7 @@ static ssize_t mei_write(struct file *file, const char __user *ubuf, timeout = write_cb->read_time + mei_secs_to_jiffies(MEI_IAMTHIF_READ_TIMER); - if (time_after(jiffies, timeout) || - cl->reading_state == MEI_READ_COMPLETE) { + if (time_after(jiffies, timeout)) { *offset = 0; mei_io_cb_free(write_cb); write_cb = NULL; @@ -344,19 +330,7 @@ static ssize_t mei_write(struct file *file, const char __user *ubuf, } } - /* free entry used in read */ - if (cl->reading_state == MEI_READ_COMPLETE) { - *offset = 0; - write_cb = mei_cl_find_read_cb(cl); - if (write_cb) { - mei_io_cb_free(write_cb); - write_cb = NULL; - cl->read_cb = NULL; - cl->reading_state = MEI_IDLE; - } - } else if (cl->reading_state == MEI_IDLE) - *offset = 0; - + *offset = 0; write_cb = mei_cl_alloc_cb(cl, length, MEI_FOP_WRITE, file); if (!write_cb) { rets = -ENOMEM; diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index 1a0f6e9..f066ecd 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -231,9 +231,9 @@ struct mei_cl_cb { * @me_client_id: me/fw id * @mei_flow_ctrl_creds: transmit flow credentials * @timer_count: watchdog timer for operation completion - * @reading_state: state of the rx * @writing_state: state of the tx - * @read_cb: current pending reading callback + * @rd_pending: pending read credits + * @rd_completed: completed read * * @device: device on the mei client bus * @device_link: link to bus clients @@ -251,9 +251,9 @@ struct mei_cl { u8 me_client_id; u8 mei_flow_ctrl_creds; u8 timer_count; - enum mei_file_transaction_states reading_state; enum mei_file_transaction_states writing_state; - struct mei_cl_cb *read_cb; + struct list_head rd_pending; + struct list_head rd_completed; /* MEI CL bus data */ struct mei_cl_device *device; @@ -425,7 +425,6 @@ const char *mei_pg_state_str(enum mei_pg_state state); * @cdev : character device * @minor : minor number allocated for device * - * @read_list : read completion list * @write_list : write pending list * @write_waiting_list : write completion list * @ctrl_wr_list : pending control write list @@ -501,7 +500,6 @@ struct mei_device { struct cdev cdev; int minor; - struct mei_cl_cb read_list; struct mei_cl_cb write_list; struct mei_cl_cb write_waiting_list; struct mei_cl_cb ctrl_wr_list; -- cgit v0.10.2 From b3de8e3719e582f3182bb504295e4a8e43c8c96f Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 10 Feb 2015 10:39:47 +0200 Subject: mei: bus: call device disable handler prior to disconnection call device's disable handler prior to disconnection so it can possibly close the communication with fw client in graceful way Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/misc/mei/bus.c b/drivers/misc/mei/bus.c index 17ca7e2..45896f9 100644 --- a/drivers/misc/mei/bus.c +++ b/drivers/misc/mei/bus.c @@ -467,37 +467,34 @@ int mei_cl_disable_device(struct mei_cl_device *device) dev = cl->dev; + if (device->ops && device->ops->disable) + device->ops->disable(device); + + device->event_cb = NULL; + mutex_lock(&dev->device_lock); if (cl->state != MEI_FILE_CONNECTED) { - mutex_unlock(&dev->device_lock); dev_err(dev->dev, "Already disconnected"); - - return 0; + err = 0; + goto out; } cl->state = MEI_FILE_DISCONNECTING; err = mei_cl_disconnect(cl); if (err < 0) { - mutex_unlock(&dev->device_lock); - dev_err(dev->dev, - "Could not disconnect from the ME client"); - - return err; + dev_err(dev->dev, "Could not disconnect from the ME client"); + goto out; } /* Flush queues and remove any pending read */ mei_cl_flush_queues(cl, NULL); - device->event_cb = NULL; - +out: mutex_unlock(&dev->device_lock); + return err; - if (!device->ops || !device->ops->disable) - return 0; - - return device->ops->disable(device); } EXPORT_SYMBOL_GPL(mei_cl_disable_device); -- cgit v0.10.2 From ed99d846f1774e14c8705819c12469eb3855b54e Mon Sep 17 00:00:00 2001 From: kbuild test robot Date: Mon, 2 Mar 2015 11:49:23 +0800 Subject: mei: bus: () can be static drivers/hv/vmbus_drv.c:51:5: sparse: symbol 'hyperv_panic_event' was not declared. Should it be static? drivers/hv/vmbus_drv.c:51:5: sparse: symbol 'hyperv_panic_event' was not declared. Should it be static? drivers/hv/vmbus_drv.c:51:5: sparse: symbol 'hyperv_panic_event' was not declared. Should it be static? drivers/hv/vmbus_drv.c:51:5: sparse: symbol 'hyperv_panic_event' was not declared. Should it be static? Signed-off-by: Fengguang Wu Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c index 526fa8b..8313e25 100644 --- a/drivers/hv/vmbus_drv.c +++ b/drivers/hv/vmbus_drv.c @@ -48,7 +48,7 @@ static struct completion probe_event; static int irq; -int hyperv_panic_event(struct notifier_block *nb, +static int hyperv_panic_event(struct notifier_block *nb, unsigned long event, void *ptr) { struct pt_regs *regs; -- cgit v0.10.2 From 112bdfaa525fd5993e17885861342893f15290b0 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Mon, 16 Feb 2015 15:41:02 +0000 Subject: extcon: arizona: Deobfuscate arizona_extcon_do_magic arizona_extcon_do_magic does not lend a lot of clarity to the purpose of the function, and as all the registers used are described in the datasheet there is no need to obfuscate the code. This patch renames the function to arizona_extcon_hp_clamp, as it controls clamping on the headphone output. Signed-off-by: Charles Keepax Acked-by: Lee Jones Signed-off-by: Chanwoo Choi diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index 63f01c4..95cf7f8 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -136,18 +136,22 @@ static const char *arizona_cable[] = { static void arizona_start_hpdet_acc_id(struct arizona_extcon_info *info); -static void arizona_extcon_do_magic(struct arizona_extcon_info *info, - unsigned int magic) +static void arizona_extcon_hp_clamp(struct arizona_extcon_info *info, + bool clamp) { struct arizona *arizona = info->arizona; + unsigned int val = 0; int ret; + if (clamp) + val = ARIZONA_RMV_SHRT_HP1L; + mutex_lock(&arizona->dapm->card->dapm_mutex); - arizona->hpdet_magic = magic; + arizona->hpdet_clamp = clamp; - /* Keep the HP output stages disabled while doing the magic */ - if (magic) { + /* Keep the HP output stages disabled while doing the clamp */ + if (clamp) { ret = regmap_update_bits(arizona->regmap, ARIZONA_OUTPUT_ENABLES_1, ARIZONA_OUT1L_ENA | @@ -158,20 +162,20 @@ static void arizona_extcon_do_magic(struct arizona_extcon_info *info, ret); } - ret = regmap_update_bits(arizona->regmap, 0x225, 0x4000, - magic); + ret = regmap_update_bits(arizona->regmap, ARIZONA_HP_CTRL_1L, + ARIZONA_RMV_SHRT_HP1L, val); if (ret != 0) - dev_warn(arizona->dev, "Failed to do magic: %d\n", + dev_warn(arizona->dev, "Failed to do clamp: %d\n", ret); - ret = regmap_update_bits(arizona->regmap, 0x226, 0x4000, - magic); + ret = regmap_update_bits(arizona->regmap, ARIZONA_HP_CTRL_1R, + ARIZONA_RMV_SHRT_HP1R, val); if (ret != 0) - dev_warn(arizona->dev, "Failed to do magic: %d\n", + dev_warn(arizona->dev, "Failed to do clamp: %d\n", ret); - /* Restore the desired state while not doing the magic */ - if (!magic) { + /* Restore the desired state while not doing the clamp */ + if (!clamp) { ret = regmap_update_bits(arizona->regmap, ARIZONA_OUTPUT_ENABLES_1, ARIZONA_OUT1L_ENA | @@ -603,7 +607,7 @@ done: ARIZONA_HP_IMPEDANCE_RANGE_MASK | ARIZONA_HP_POLL, 0); - arizona_extcon_do_magic(info, 0); + arizona_extcon_hp_clamp(info, false); if (id_gpio) gpio_set_value_cansleep(id_gpio, 0); @@ -648,7 +652,7 @@ static void arizona_identify_headphone(struct arizona_extcon_info *info) if (info->mic) arizona_stop_mic(info); - arizona_extcon_do_magic(info, 0x4000); + arizona_extcon_hp_clamp(info, true); ret = regmap_update_bits(arizona->regmap, ARIZONA_ACCESSORY_DETECT_MODE_1, @@ -699,7 +703,7 @@ static void arizona_start_hpdet_acc_id(struct arizona_extcon_info *info) info->hpdet_active = true; - arizona_extcon_do_magic(info, 0x4000); + arizona_extcon_hp_clamp(info, true); ret = regmap_update_bits(arizona->regmap, ARIZONA_ACCESSORY_DETECT_MODE_1, diff --git a/include/linux/mfd/arizona/core.h b/include/linux/mfd/arizona/core.h index 910e3aa..4863548 100644 --- a/include/linux/mfd/arizona/core.h +++ b/include/linux/mfd/arizona/core.h @@ -126,7 +126,7 @@ struct arizona { struct regmap_irq_chip_data *aod_irq_chip; struct regmap_irq_chip_data *irq_chip; - bool hpdet_magic; + bool hpdet_clamp; unsigned int hp_ena; struct mutex clk_lock; diff --git a/sound/soc/codecs/arizona.c b/sound/soc/codecs/arizona.c index 2920261..fb58c7e 100644 --- a/sound/soc/codecs/arizona.c +++ b/sound/soc/codecs/arizona.c @@ -840,8 +840,8 @@ int arizona_hp_ev(struct snd_soc_dapm_widget *w, priv->arizona->hp_ena &= ~mask; priv->arizona->hp_ena |= val; - /* Force off if HPDET magic is active */ - if (priv->arizona->hpdet_magic) + /* Force off if HPDET clamp is active */ + if (priv->arizona->hpdet_clamp) val = 0; regmap_update_bits_async(arizona->regmap, ARIZONA_OUTPUT_ENABLES_1, -- cgit v0.10.2 From 43f0acd96163754672cfb8c8015c54ec527a2cce Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Mon, 16 Feb 2015 15:41:03 +0000 Subject: extcon: arizona: Fix headphone clamping on wm5110 wm5110 requires slightly different configuration of the headphone clamps to other Arizona devices. Otherwise headphone detection accuracy will be way off. This patch adds the needed clamping. Signed-off-by: Charles Keepax Signed-off-by: Chanwoo Choi diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index 95cf7f8..d9e763c 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -140,11 +140,24 @@ static void arizona_extcon_hp_clamp(struct arizona_extcon_info *info, bool clamp) { struct arizona *arizona = info->arizona; - unsigned int val = 0; + unsigned int mask = 0, val = 0; int ret; - if (clamp) - val = ARIZONA_RMV_SHRT_HP1L; + switch (arizona->type) { + case WM5110: + mask = ARIZONA_HP1L_SHRTO | ARIZONA_HP1L_FLWR | + ARIZONA_HP1L_SHRTI; + if (clamp) + val = ARIZONA_HP1L_SHRTO; + else + val = ARIZONA_HP1L_FLWR | ARIZONA_HP1L_SHRTI; + break; + default: + mask = ARIZONA_RMV_SHRT_HP1L; + if (clamp) + val = ARIZONA_RMV_SHRT_HP1L; + break; + }; mutex_lock(&arizona->dapm->card->dapm_mutex); @@ -163,13 +176,13 @@ static void arizona_extcon_hp_clamp(struct arizona_extcon_info *info, } ret = regmap_update_bits(arizona->regmap, ARIZONA_HP_CTRL_1L, - ARIZONA_RMV_SHRT_HP1L, val); + mask, val); if (ret != 0) dev_warn(arizona->dev, "Failed to do clamp: %d\n", ret); ret = regmap_update_bits(arizona->regmap, ARIZONA_HP_CTRL_1R, - ARIZONA_RMV_SHRT_HP1R, val); + mask, val); if (ret != 0) dev_warn(arizona->dev, "Failed to do clamp: %d\n", ret); -- cgit v0.10.2 From b43baf694fde30dc2db2386c347324d1a013f3d1 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Wed, 4 Mar 2015 18:41:34 +0200 Subject: mei: free me client references on host init Fx fixes leak introduced by: commit b7d885145538 ("mei: revamp me clients list handling") Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c index 98a5363..b6fec4d 100644 --- a/drivers/misc/mei/client.c +++ b/drivers/misc/mei/client.c @@ -668,14 +668,17 @@ void mei_host_client_init(struct work_struct *work) me_cl = mei_me_cl_by_uuid(dev, &mei_amthif_guid); if (me_cl) mei_amthif_host_init(dev); + mei_me_cl_put(me_cl); me_cl = mei_me_cl_by_uuid(dev, &mei_wd_guid); if (me_cl) mei_wd_host_init(dev); + mei_me_cl_put(me_cl); me_cl = mei_me_cl_by_uuid(dev, &mei_nfc_guid); if (me_cl) mei_nfc_host_init(dev); + mei_me_cl_put(me_cl); dev->dev_state = MEI_DEV_ENABLED; -- cgit v0.10.2 From d2b5851d8583e690eeb5ac8dfff5da92e1f1468f Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Wed, 4 Mar 2015 18:41:35 +0200 Subject: mei: trace: fix missing include to linux/device.h Fix warning (discovered using randconfig) drivers/misc/mei/mei-trace.h:30:24: warning: 'struct device' declared inside parameter list TP_PROTO(const struct device *dev, const char *reg, u32 offs, u32 val), Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/misc/mei/mei-trace.h b/drivers/misc/mei/mei-trace.h index d5c38d1..5f4e1a1 100644 --- a/drivers/misc/mei/mei-trace.h +++ b/drivers/misc/mei/mei-trace.h @@ -21,6 +21,8 @@ #include #include +#include + #undef TRACE_SYSTEM #define TRACE_SYSTEM mei -- cgit v0.10.2 From f68a8342b1082d9a4b084cb085396e562899bc62 Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Sat, 7 Mar 2015 22:58:10 +0900 Subject: extcon: Rename extcon core driver This patch renames the extcon core driver from extcon-class.c to extcon.c because '-class' postfix is not necessary. Signed-off-by: Chanwoo Choi diff --git a/drivers/extcon/Makefile b/drivers/extcon/Makefile index e1eb2d5..9204114 100644 --- a/drivers/extcon/Makefile +++ b/drivers/extcon/Makefile @@ -2,7 +2,7 @@ # Makefile for external connector class (extcon) devices # -obj-$(CONFIG_EXTCON) += extcon-class.o +obj-$(CONFIG_EXTCON) += extcon.o obj-$(CONFIG_EXTCON_ADC_JACK) += extcon-adc-jack.o obj-$(CONFIG_EXTCON_ARIZONA) += extcon-arizona.o obj-$(CONFIG_EXTCON_GPIO) += extcon-gpio.o diff --git a/drivers/extcon/extcon-class.c b/drivers/extcon/extcon-class.c deleted file mode 100644 index 8319f25..0000000 --- a/drivers/extcon/extcon-class.c +++ /dev/null @@ -1,1038 +0,0 @@ -/* - * drivers/extcon/extcon_class.c - * - * External connector (extcon) class driver - * - * Copyright (C) 2012 Samsung Electronics - * Author: Donggeun Kim - * Author: MyungJoo Ham - * - * based on android/drivers/switch/switch_class.c - * Copyright (C) 2008 Google, Inc. - * Author: Mike Lockwood - * - * This software is licensed under the terms of the GNU General Public - * License version 2, as published by the Free Software Foundation, and - * may be copied, distributed, and modified under those terms. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * -*/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* - * extcon_cable_name suggests the standard cable names for commonly used - * cable types. - * - * However, please do not use extcon_cable_name directly for extcon_dev - * struct's supported_cable pointer unless your device really supports - * every single port-type of the following cable names. Please choose cable - * names that are actually used in your extcon device. - */ -const char extcon_cable_name[][CABLE_NAME_MAX + 1] = { - [EXTCON_USB] = "USB", - [EXTCON_USB_HOST] = "USB-Host", - [EXTCON_TA] = "TA", - [EXTCON_FAST_CHARGER] = "Fast-charger", - [EXTCON_SLOW_CHARGER] = "Slow-charger", - [EXTCON_CHARGE_DOWNSTREAM] = "Charge-downstream", - [EXTCON_HDMI] = "HDMI", - [EXTCON_MHL] = "MHL", - [EXTCON_DVI] = "DVI", - [EXTCON_VGA] = "VGA", - [EXTCON_DOCK] = "Dock", - [EXTCON_LINE_IN] = "Line-in", - [EXTCON_LINE_OUT] = "Line-out", - [EXTCON_MIC_IN] = "Microphone", - [EXTCON_HEADPHONE_OUT] = "Headphone", - [EXTCON_SPDIF_IN] = "SPDIF-in", - [EXTCON_SPDIF_OUT] = "SPDIF-out", - [EXTCON_VIDEO_IN] = "Video-in", - [EXTCON_VIDEO_OUT] = "Video-out", - [EXTCON_MECHANICAL] = "Mechanical", -}; - -static struct class *extcon_class; -#if defined(CONFIG_ANDROID) -static struct class_compat *switch_class; -#endif /* CONFIG_ANDROID */ - -static LIST_HEAD(extcon_dev_list); -static DEFINE_MUTEX(extcon_dev_list_lock); - -/** - * check_mutually_exclusive - Check if new_state violates mutually_exclusive - * condition. - * @edev: the extcon device - * @new_state: new cable attach status for @edev - * - * Returns 0 if nothing violates. Returns the index + 1 for the first - * violated condition. - */ -static int check_mutually_exclusive(struct extcon_dev *edev, u32 new_state) -{ - int i = 0; - - if (!edev->mutually_exclusive) - return 0; - - for (i = 0; edev->mutually_exclusive[i]; i++) { - int weight; - u32 correspondants = new_state & edev->mutually_exclusive[i]; - - /* calculate the total number of bits set */ - weight = hweight32(correspondants); - if (weight > 1) - return i + 1; - } - - return 0; -} - -static ssize_t state_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - int i, count = 0; - struct extcon_dev *edev = dev_get_drvdata(dev); - - if (edev->print_state) { - int ret = edev->print_state(edev, buf); - - if (ret >= 0) - return ret; - /* Use default if failed */ - } - - if (edev->max_supported == 0) - return sprintf(buf, "%u\n", edev->state); - - for (i = 0; i < SUPPORTED_CABLE_MAX; i++) { - if (!edev->supported_cable[i]) - break; - count += sprintf(buf + count, "%s=%d\n", - edev->supported_cable[i], - !!(edev->state & (1 << i))); - } - - return count; -} - -static ssize_t state_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) -{ - u32 state; - ssize_t ret = 0; - struct extcon_dev *edev = dev_get_drvdata(dev); - - ret = sscanf(buf, "0x%x", &state); - if (ret == 0) - ret = -EINVAL; - else - ret = extcon_set_state(edev, state); - - if (ret < 0) - return ret; - - return count; -} -static DEVICE_ATTR_RW(state); - -static ssize_t name_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct extcon_dev *edev = dev_get_drvdata(dev); - - /* Optional callback given by the user */ - if (edev->print_name) { - int ret = edev->print_name(edev, buf); - if (ret >= 0) - return ret; - } - - return sprintf(buf, "%s\n", dev_name(&edev->dev)); -} -static DEVICE_ATTR_RO(name); - -static ssize_t cable_name_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct extcon_cable *cable = container_of(attr, struct extcon_cable, - attr_name); - - return sprintf(buf, "%s\n", - cable->edev->supported_cable[cable->cable_index]); -} - -static ssize_t cable_state_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct extcon_cable *cable = container_of(attr, struct extcon_cable, - attr_state); - - return sprintf(buf, "%d\n", - extcon_get_cable_state_(cable->edev, - cable->cable_index)); -} - -/** - * extcon_update_state() - Update the cable attach states of the extcon device - * only for the masked bits. - * @edev: the extcon device - * @mask: the bit mask to designate updated bits. - * @state: new cable attach status for @edev - * - * Changing the state sends uevent with environment variable containing - * the name of extcon device (envp[0]) and the state output (envp[1]). - * Tizen uses this format for extcon device to get events from ports. - * Android uses this format as well. - * - * Note that the notifier provides which bits are changed in the state - * variable with the val parameter (second) to the callback. - */ -int extcon_update_state(struct extcon_dev *edev, u32 mask, u32 state) -{ - char name_buf[120]; - char state_buf[120]; - char *prop_buf; - char *envp[3]; - int env_offset = 0; - int length; - unsigned long flags; - - spin_lock_irqsave(&edev->lock, flags); - - if (edev->state != ((edev->state & ~mask) | (state & mask))) { - u32 old_state = edev->state; - - if (check_mutually_exclusive(edev, (edev->state & ~mask) | - (state & mask))) { - spin_unlock_irqrestore(&edev->lock, flags); - return -EPERM; - } - - edev->state &= ~mask; - edev->state |= state & mask; - - raw_notifier_call_chain(&edev->nh, old_state, edev); - /* This could be in interrupt handler */ - prop_buf = (char *)get_zeroed_page(GFP_ATOMIC); - if (prop_buf) { - length = name_show(&edev->dev, NULL, prop_buf); - if (length > 0) { - if (prop_buf[length - 1] == '\n') - prop_buf[length - 1] = 0; - snprintf(name_buf, sizeof(name_buf), - "NAME=%s", prop_buf); - envp[env_offset++] = name_buf; - } - length = state_show(&edev->dev, NULL, prop_buf); - if (length > 0) { - if (prop_buf[length - 1] == '\n') - prop_buf[length - 1] = 0; - snprintf(state_buf, sizeof(state_buf), - "STATE=%s", prop_buf); - envp[env_offset++] = state_buf; - } - envp[env_offset] = NULL; - /* Unlock early before uevent */ - spin_unlock_irqrestore(&edev->lock, flags); - - kobject_uevent_env(&edev->dev.kobj, KOBJ_CHANGE, envp); - free_page((unsigned long)prop_buf); - } else { - /* Unlock early before uevent */ - spin_unlock_irqrestore(&edev->lock, flags); - - dev_err(&edev->dev, "out of memory in extcon_set_state\n"); - kobject_uevent(&edev->dev.kobj, KOBJ_CHANGE); - } - } else { - /* No changes */ - spin_unlock_irqrestore(&edev->lock, flags); - } - - return 0; -} -EXPORT_SYMBOL_GPL(extcon_update_state); - -/** - * extcon_set_state() - Set the cable attach states of the extcon device. - * @edev: the extcon device - * @state: new cable attach status for @edev - * - * Note that notifier provides which bits are changed in the state - * variable with the val parameter (second) to the callback. - */ -int extcon_set_state(struct extcon_dev *edev, u32 state) -{ - return extcon_update_state(edev, 0xffffffff, state); -} -EXPORT_SYMBOL_GPL(extcon_set_state); - -/** - * extcon_find_cable_index() - Get the cable index based on the cable name. - * @edev: the extcon device that has the cable. - * @cable_name: cable name to be searched. - * - * Note that accessing a cable state based on cable_index is faster than - * cable_name because using cable_name induces a loop with strncmp(). - * Thus, when get/set_cable_state is repeatedly used, using cable_index - * is recommended. - */ -int extcon_find_cable_index(struct extcon_dev *edev, const char *cable_name) -{ - int i; - - if (edev->supported_cable) { - for (i = 0; edev->supported_cable[i]; i++) { - if (!strncmp(edev->supported_cable[i], - cable_name, CABLE_NAME_MAX)) - return i; - } - } - - return -EINVAL; -} -EXPORT_SYMBOL_GPL(extcon_find_cable_index); - -/** - * extcon_get_cable_state_() - Get the status of a specific cable. - * @edev: the extcon device that has the cable. - * @index: cable index that can be retrieved by extcon_find_cable_index(). - */ -int extcon_get_cable_state_(struct extcon_dev *edev, int index) -{ - if (index < 0 || (edev->max_supported && edev->max_supported <= index)) - return -EINVAL; - - return !!(edev->state & (1 << index)); -} -EXPORT_SYMBOL_GPL(extcon_get_cable_state_); - -/** - * extcon_get_cable_state() - Get the status of a specific cable. - * @edev: the extcon device that has the cable. - * @cable_name: cable name. - * - * Note that this is slower than extcon_get_cable_state_. - */ -int extcon_get_cable_state(struct extcon_dev *edev, const char *cable_name) -{ - return extcon_get_cable_state_(edev, extcon_find_cable_index - (edev, cable_name)); -} -EXPORT_SYMBOL_GPL(extcon_get_cable_state); - -/** - * extcon_set_cable_state_() - Set the status of a specific cable. - * @edev: the extcon device that has the cable. - * @index: cable index that can be retrieved by - * extcon_find_cable_index(). - * @cable_state: the new cable status. The default semantics is - * true: attached / false: detached. - */ -int extcon_set_cable_state_(struct extcon_dev *edev, - int index, bool cable_state) -{ - u32 state; - - if (index < 0 || (edev->max_supported && edev->max_supported <= index)) - return -EINVAL; - - state = cable_state ? (1 << index) : 0; - return extcon_update_state(edev, 1 << index, state); -} -EXPORT_SYMBOL_GPL(extcon_set_cable_state_); - -/** - * extcon_set_cable_state() - Set the status of a specific cable. - * @edev: the extcon device that has the cable. - * @cable_name: cable name. - * @cable_state: the new cable status. The default semantics is - * true: attached / false: detached. - * - * Note that this is slower than extcon_set_cable_state_. - */ -int extcon_set_cable_state(struct extcon_dev *edev, - const char *cable_name, bool cable_state) -{ - return extcon_set_cable_state_(edev, extcon_find_cable_index - (edev, cable_name), cable_state); -} -EXPORT_SYMBOL_GPL(extcon_set_cable_state); - -/** - * extcon_get_extcon_dev() - Get the extcon device instance from the name - * @extcon_name: The extcon name provided with extcon_dev_register() - */ -struct extcon_dev *extcon_get_extcon_dev(const char *extcon_name) -{ - struct extcon_dev *sd; - - mutex_lock(&extcon_dev_list_lock); - list_for_each_entry(sd, &extcon_dev_list, entry) { - if (!strcmp(sd->name, extcon_name)) - goto out; - } - sd = NULL; -out: - mutex_unlock(&extcon_dev_list_lock); - return sd; -} -EXPORT_SYMBOL_GPL(extcon_get_extcon_dev); - -static int _call_per_cable(struct notifier_block *nb, unsigned long val, - void *ptr) -{ - struct extcon_specific_cable_nb *obj = container_of(nb, - struct extcon_specific_cable_nb, internal_nb); - struct extcon_dev *edev = ptr; - - if ((val & (1 << obj->cable_index)) != - (edev->state & (1 << obj->cable_index))) { - bool cable_state = true; - - obj->previous_value = val; - - if (val & (1 << obj->cable_index)) - cable_state = false; - - return obj->user_nb->notifier_call(obj->user_nb, - cable_state, ptr); - } - - return NOTIFY_OK; -} - -/** - * extcon_register_interest() - Register a notifier for a state change of a - * specific cable, not an entier set of cables of a - * extcon device. - * @obj: an empty extcon_specific_cable_nb object to be returned. - * @extcon_name: the name of extcon device. - * if NULL, extcon_register_interest will register - * every cable with the target cable_name given. - * @cable_name: the target cable name. - * @nb: the notifier block to get notified. - * - * Provide an empty extcon_specific_cable_nb. extcon_register_interest() sets - * the struct for you. - * - * extcon_register_interest is a helper function for those who want to get - * notification for a single specific cable's status change. If a user wants - * to get notification for any changes of all cables of a extcon device, - * he/she should use the general extcon_register_notifier(). - * - * Note that the second parameter given to the callback of nb (val) is - * "old_state", not the current state. The current state can be retrieved - * by looking at the third pameter (edev pointer)'s state value. - */ -int extcon_register_interest(struct extcon_specific_cable_nb *obj, - const char *extcon_name, const char *cable_name, - struct notifier_block *nb) -{ - if (!obj || !cable_name || !nb) - return -EINVAL; - - if (extcon_name) { - obj->edev = extcon_get_extcon_dev(extcon_name); - if (!obj->edev) - return -ENODEV; - - obj->cable_index = extcon_find_cable_index(obj->edev, - cable_name); - if (obj->cable_index < 0) - return obj->cable_index; - - obj->user_nb = nb; - - obj->internal_nb.notifier_call = _call_per_cable; - - return raw_notifier_chain_register(&obj->edev->nh, - &obj->internal_nb); - } else { - struct class_dev_iter iter; - struct extcon_dev *extd; - struct device *dev; - - if (!extcon_class) - return -ENODEV; - class_dev_iter_init(&iter, extcon_class, NULL, NULL); - while ((dev = class_dev_iter_next(&iter))) { - extd = dev_get_drvdata(dev); - - if (extcon_find_cable_index(extd, cable_name) < 0) - continue; - - class_dev_iter_exit(&iter); - return extcon_register_interest(obj, extd->name, - cable_name, nb); - } - - return -ENODEV; - } -} -EXPORT_SYMBOL_GPL(extcon_register_interest); - -/** - * extcon_unregister_interest() - Unregister the notifier registered by - * extcon_register_interest(). - * @obj: the extcon_specific_cable_nb object returned by - * extcon_register_interest(). - */ -int extcon_unregister_interest(struct extcon_specific_cable_nb *obj) -{ - if (!obj) - return -EINVAL; - - return raw_notifier_chain_unregister(&obj->edev->nh, &obj->internal_nb); -} -EXPORT_SYMBOL_GPL(extcon_unregister_interest); - -/** - * extcon_register_notifier() - Register a notifiee to get notified by - * any attach status changes from the extcon. - * @edev: the extcon device. - * @nb: a notifier block to be registered. - * - * Note that the second parameter given to the callback of nb (val) is - * "old_state", not the current state. The current state can be retrieved - * by looking at the third pameter (edev pointer)'s state value. - */ -int extcon_register_notifier(struct extcon_dev *edev, - struct notifier_block *nb) -{ - return raw_notifier_chain_register(&edev->nh, nb); -} -EXPORT_SYMBOL_GPL(extcon_register_notifier); - -/** - * extcon_unregister_notifier() - Unregister a notifiee from the extcon device. - * @edev: the extcon device. - * @nb: a registered notifier block to be unregistered. - */ -int extcon_unregister_notifier(struct extcon_dev *edev, - struct notifier_block *nb) -{ - return raw_notifier_chain_unregister(&edev->nh, nb); -} -EXPORT_SYMBOL_GPL(extcon_unregister_notifier); - -static struct attribute *extcon_attrs[] = { - &dev_attr_state.attr, - &dev_attr_name.attr, - NULL, -}; -ATTRIBUTE_GROUPS(extcon); - -static int create_extcon_class(void) -{ - if (!extcon_class) { - extcon_class = class_create(THIS_MODULE, "extcon"); - if (IS_ERR(extcon_class)) - return PTR_ERR(extcon_class); - extcon_class->dev_groups = extcon_groups; - -#if defined(CONFIG_ANDROID) - switch_class = class_compat_register("switch"); - if (WARN(!switch_class, "cannot allocate")) - return -ENOMEM; -#endif /* CONFIG_ANDROID */ - } - - return 0; -} - -static void extcon_dev_release(struct device *dev) -{ -} - -static const char *muex_name = "mutually_exclusive"; -static void dummy_sysfs_dev_release(struct device *dev) -{ -} - -/* - * extcon_dev_allocate() - Allocate the memory of extcon device. - * @supported_cable: Array of supported cable names ending with NULL. - * If supported_cable is NULL, cable name related APIs - * are disabled. - * - * This function allocates the memory for extcon device without allocating - * memory in each extcon provider driver and initialize default setting for - * extcon device. - * - * Return the pointer of extcon device if success or ERR_PTR(err) if fail - */ -struct extcon_dev *extcon_dev_allocate(const char **supported_cable) -{ - struct extcon_dev *edev; - - edev = kzalloc(sizeof(*edev), GFP_KERNEL); - if (!edev) - return ERR_PTR(-ENOMEM); - - edev->max_supported = 0; - edev->supported_cable = supported_cable; - - return edev; -} - -/* - * extcon_dev_free() - Free the memory of extcon device. - * @edev: the extcon device to free - */ -void extcon_dev_free(struct extcon_dev *edev) -{ - kfree(edev); -} -EXPORT_SYMBOL_GPL(extcon_dev_free); - -static int devm_extcon_dev_match(struct device *dev, void *res, void *data) -{ - struct extcon_dev **r = res; - - if (WARN_ON(!r || !*r)) - return 0; - - return *r == data; -} - -static void devm_extcon_dev_release(struct device *dev, void *res) -{ - extcon_dev_free(*(struct extcon_dev **)res); -} - -/** - * devm_extcon_dev_allocate - Allocate managed extcon device - * @dev: device owning the extcon device being created - * @supported_cable: Array of supported cable names ending with NULL. - * If supported_cable is NULL, cable name related APIs - * are disabled. - * - * This function manages automatically the memory of extcon device using device - * resource management and simplify the control of freeing the memory of extcon - * device. - * - * Returns the pointer memory of allocated extcon_dev if success - * or ERR_PTR(err) if fail - */ -struct extcon_dev *devm_extcon_dev_allocate(struct device *dev, - const char **supported_cable) -{ - struct extcon_dev **ptr, *edev; - - ptr = devres_alloc(devm_extcon_dev_release, sizeof(*ptr), GFP_KERNEL); - if (!ptr) - return ERR_PTR(-ENOMEM); - - edev = extcon_dev_allocate(supported_cable); - if (IS_ERR(edev)) { - devres_free(ptr); - return edev; - } - - edev->dev.parent = dev; - - *ptr = edev; - devres_add(dev, ptr); - - return edev; -} -EXPORT_SYMBOL_GPL(devm_extcon_dev_allocate); - -void devm_extcon_dev_free(struct device *dev, struct extcon_dev *edev) -{ - WARN_ON(devres_release(dev, devm_extcon_dev_release, - devm_extcon_dev_match, edev)); -} -EXPORT_SYMBOL_GPL(devm_extcon_dev_free); - -/** - * extcon_dev_register() - Register a new extcon device - * @edev : the new extcon device (should be allocated before calling) - * - * Among the members of edev struct, please set the "user initializing data" - * in any case and set the "optional callbacks" if required. However, please - * do not set the values of "internal data", which are initialized by - * this function. - */ -int extcon_dev_register(struct extcon_dev *edev) -{ - int ret, index = 0; - - if (!extcon_class) { - ret = create_extcon_class(); - if (ret < 0) - return ret; - } - - if (edev->supported_cable) { - /* Get size of array */ - for (index = 0; edev->supported_cable[index]; index++) - ; - edev->max_supported = index; - } else { - edev->max_supported = 0; - } - - if (index > SUPPORTED_CABLE_MAX) { - dev_err(&edev->dev, "extcon: maximum number of supported cables exceeded.\n"); - return -EINVAL; - } - - edev->dev.class = extcon_class; - edev->dev.release = extcon_dev_release; - - edev->name = edev->name ? edev->name : dev_name(edev->dev.parent); - if (IS_ERR_OR_NULL(edev->name)) { - dev_err(&edev->dev, - "extcon device name is null\n"); - return -EINVAL; - } - dev_set_name(&edev->dev, "%s", edev->name); - - if (edev->max_supported) { - char buf[10]; - char *str; - struct extcon_cable *cable; - - edev->cables = kzalloc(sizeof(struct extcon_cable) * - edev->max_supported, GFP_KERNEL); - if (!edev->cables) { - ret = -ENOMEM; - goto err_sysfs_alloc; - } - for (index = 0; index < edev->max_supported; index++) { - cable = &edev->cables[index]; - - snprintf(buf, 10, "cable.%d", index); - str = kzalloc(sizeof(char) * (strlen(buf) + 1), - GFP_KERNEL); - if (!str) { - for (index--; index >= 0; index--) { - cable = &edev->cables[index]; - kfree(cable->attr_g.name); - } - ret = -ENOMEM; - - goto err_alloc_cables; - } - strcpy(str, buf); - - cable->edev = edev; - cable->cable_index = index; - cable->attrs[0] = &cable->attr_name.attr; - cable->attrs[1] = &cable->attr_state.attr; - cable->attrs[2] = NULL; - cable->attr_g.name = str; - cable->attr_g.attrs = cable->attrs; - - sysfs_attr_init(&cable->attr_name.attr); - cable->attr_name.attr.name = "name"; - cable->attr_name.attr.mode = 0444; - cable->attr_name.show = cable_name_show; - - sysfs_attr_init(&cable->attr_state.attr); - cable->attr_state.attr.name = "state"; - cable->attr_state.attr.mode = 0444; - cable->attr_state.show = cable_state_show; - } - } - - if (edev->max_supported && edev->mutually_exclusive) { - char buf[80]; - char *name; - - /* Count the size of mutually_exclusive array */ - for (index = 0; edev->mutually_exclusive[index]; index++) - ; - - edev->attrs_muex = kzalloc(sizeof(struct attribute *) * - (index + 1), GFP_KERNEL); - if (!edev->attrs_muex) { - ret = -ENOMEM; - goto err_muex; - } - - edev->d_attrs_muex = kzalloc(sizeof(struct device_attribute) * - index, GFP_KERNEL); - if (!edev->d_attrs_muex) { - ret = -ENOMEM; - kfree(edev->attrs_muex); - goto err_muex; - } - - for (index = 0; edev->mutually_exclusive[index]; index++) { - sprintf(buf, "0x%x", edev->mutually_exclusive[index]); - name = kzalloc(sizeof(char) * (strlen(buf) + 1), - GFP_KERNEL); - if (!name) { - for (index--; index >= 0; index--) { - kfree(edev->d_attrs_muex[index].attr. - name); - } - kfree(edev->d_attrs_muex); - kfree(edev->attrs_muex); - ret = -ENOMEM; - goto err_muex; - } - strcpy(name, buf); - sysfs_attr_init(&edev->d_attrs_muex[index].attr); - edev->d_attrs_muex[index].attr.name = name; - edev->d_attrs_muex[index].attr.mode = 0000; - edev->attrs_muex[index] = &edev->d_attrs_muex[index] - .attr; - } - edev->attr_g_muex.name = muex_name; - edev->attr_g_muex.attrs = edev->attrs_muex; - - } - - if (edev->max_supported) { - edev->extcon_dev_type.groups = - kzalloc(sizeof(struct attribute_group *) * - (edev->max_supported + 2), GFP_KERNEL); - if (!edev->extcon_dev_type.groups) { - ret = -ENOMEM; - goto err_alloc_groups; - } - - edev->extcon_dev_type.name = dev_name(&edev->dev); - edev->extcon_dev_type.release = dummy_sysfs_dev_release; - - for (index = 0; index < edev->max_supported; index++) - edev->extcon_dev_type.groups[index] = - &edev->cables[index].attr_g; - if (edev->mutually_exclusive) - edev->extcon_dev_type.groups[index] = - &edev->attr_g_muex; - - edev->dev.type = &edev->extcon_dev_type; - } - - ret = device_register(&edev->dev); - if (ret) { - put_device(&edev->dev); - goto err_dev; - } -#if defined(CONFIG_ANDROID) - if (switch_class) - ret = class_compat_create_link(switch_class, &edev->dev, NULL); -#endif /* CONFIG_ANDROID */ - - spin_lock_init(&edev->lock); - - RAW_INIT_NOTIFIER_HEAD(&edev->nh); - - dev_set_drvdata(&edev->dev, edev); - edev->state = 0; - - mutex_lock(&extcon_dev_list_lock); - list_add(&edev->entry, &extcon_dev_list); - mutex_unlock(&extcon_dev_list_lock); - - return 0; - -err_dev: - if (edev->max_supported) - kfree(edev->extcon_dev_type.groups); -err_alloc_groups: - if (edev->max_supported && edev->mutually_exclusive) { - for (index = 0; edev->mutually_exclusive[index]; index++) - kfree(edev->d_attrs_muex[index].attr.name); - kfree(edev->d_attrs_muex); - kfree(edev->attrs_muex); - } -err_muex: - for (index = 0; index < edev->max_supported; index++) - kfree(edev->cables[index].attr_g.name); -err_alloc_cables: - if (edev->max_supported) - kfree(edev->cables); -err_sysfs_alloc: - return ret; -} -EXPORT_SYMBOL_GPL(extcon_dev_register); - -/** - * extcon_dev_unregister() - Unregister the extcon device. - * @edev: the extcon device instance to be unregistered. - * - * Note that this does not call kfree(edev) because edev was not allocated - * by this class. - */ -void extcon_dev_unregister(struct extcon_dev *edev) -{ - int index; - - mutex_lock(&extcon_dev_list_lock); - list_del(&edev->entry); - mutex_unlock(&extcon_dev_list_lock); - - if (IS_ERR_OR_NULL(get_device(&edev->dev))) { - dev_err(&edev->dev, "Failed to unregister extcon_dev (%s)\n", - dev_name(&edev->dev)); - return; - } - - device_unregister(&edev->dev); - - if (edev->mutually_exclusive && edev->max_supported) { - for (index = 0; edev->mutually_exclusive[index]; - index++) - kfree(edev->d_attrs_muex[index].attr.name); - kfree(edev->d_attrs_muex); - kfree(edev->attrs_muex); - } - - for (index = 0; index < edev->max_supported; index++) - kfree(edev->cables[index].attr_g.name); - - if (edev->max_supported) { - kfree(edev->extcon_dev_type.groups); - kfree(edev->cables); - } - -#if defined(CONFIG_ANDROID) - if (switch_class) - class_compat_remove_link(switch_class, &edev->dev, NULL); -#endif - put_device(&edev->dev); -} -EXPORT_SYMBOL_GPL(extcon_dev_unregister); - -static void devm_extcon_dev_unreg(struct device *dev, void *res) -{ - extcon_dev_unregister(*(struct extcon_dev **)res); -} - -/** - * devm_extcon_dev_register() - Resource-managed extcon_dev_register() - * @dev: device to allocate extcon device - * @edev: the new extcon device to register - * - * Managed extcon_dev_register() function. If extcon device is attached with - * this function, that extcon device is automatically unregistered on driver - * detach. Internally this function calls extcon_dev_register() function. - * To get more information, refer that function. - * - * If extcon device is registered with this function and the device needs to be - * unregistered separately, devm_extcon_dev_unregister() should be used. - * - * Returns 0 if success or negaive error number if failure. - */ -int devm_extcon_dev_register(struct device *dev, struct extcon_dev *edev) -{ - struct extcon_dev **ptr; - int ret; - - ptr = devres_alloc(devm_extcon_dev_unreg, sizeof(*ptr), GFP_KERNEL); - if (!ptr) - return -ENOMEM; - - ret = extcon_dev_register(edev); - if (ret) { - devres_free(ptr); - return ret; - } - - *ptr = edev; - devres_add(dev, ptr); - - return 0; -} -EXPORT_SYMBOL_GPL(devm_extcon_dev_register); - -/** - * devm_extcon_dev_unregister() - Resource-managed extcon_dev_unregister() - * @dev: device the extcon belongs to - * @edev: the extcon device to unregister - * - * Unregister extcon device that is registered with devm_extcon_dev_register() - * function. - */ -void devm_extcon_dev_unregister(struct device *dev, struct extcon_dev *edev) -{ - WARN_ON(devres_release(dev, devm_extcon_dev_unreg, - devm_extcon_dev_match, edev)); -} -EXPORT_SYMBOL_GPL(devm_extcon_dev_unregister); - -#ifdef CONFIG_OF -/* - * extcon_get_edev_by_phandle - Get the extcon device from devicetree - * @dev - instance to the given device - * @index - index into list of extcon_dev - * - * return the instance of extcon device - */ -struct extcon_dev *extcon_get_edev_by_phandle(struct device *dev, int index) -{ - struct device_node *node; - struct extcon_dev *edev; - - if (!dev->of_node) { - dev_err(dev, "device does not have a device node entry\n"); - return ERR_PTR(-EINVAL); - } - - node = of_parse_phandle(dev->of_node, "extcon", index); - if (!node) { - dev_err(dev, "failed to get phandle in %s node\n", - dev->of_node->full_name); - return ERR_PTR(-ENODEV); - } - - mutex_lock(&extcon_dev_list_lock); - list_for_each_entry(edev, &extcon_dev_list, entry) { - if (edev->dev.parent && edev->dev.parent->of_node == node) { - mutex_unlock(&extcon_dev_list_lock); - return edev; - } - } - mutex_unlock(&extcon_dev_list_lock); - - return ERR_PTR(-EPROBE_DEFER); -} -#else -struct extcon_dev *extcon_get_edev_by_phandle(struct device *dev, int index) -{ - return ERR_PTR(-ENOSYS); -} -#endif /* CONFIG_OF */ -EXPORT_SYMBOL_GPL(extcon_get_edev_by_phandle); - -static int __init extcon_class_init(void) -{ - return create_extcon_class(); -} -module_init(extcon_class_init); - -static void __exit extcon_class_exit(void) -{ -#if defined(CONFIG_ANDROID) - class_compat_unregister(switch_class); -#endif - class_destroy(extcon_class); -} -module_exit(extcon_class_exit); - -MODULE_AUTHOR("Mike Lockwood "); -MODULE_AUTHOR("Donggeun Kim "); -MODULE_AUTHOR("MyungJoo Ham "); -MODULE_DESCRIPTION("External connector (extcon) class driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/extcon/extcon.c b/drivers/extcon/extcon.c new file mode 100644 index 0000000..8319f25 --- /dev/null +++ b/drivers/extcon/extcon.c @@ -0,0 +1,1038 @@ +/* + * drivers/extcon/extcon_class.c + * + * External connector (extcon) class driver + * + * Copyright (C) 2012 Samsung Electronics + * Author: Donggeun Kim + * Author: MyungJoo Ham + * + * based on android/drivers/switch/switch_class.c + * Copyright (C) 2008 Google, Inc. + * Author: Mike Lockwood + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * extcon_cable_name suggests the standard cable names for commonly used + * cable types. + * + * However, please do not use extcon_cable_name directly for extcon_dev + * struct's supported_cable pointer unless your device really supports + * every single port-type of the following cable names. Please choose cable + * names that are actually used in your extcon device. + */ +const char extcon_cable_name[][CABLE_NAME_MAX + 1] = { + [EXTCON_USB] = "USB", + [EXTCON_USB_HOST] = "USB-Host", + [EXTCON_TA] = "TA", + [EXTCON_FAST_CHARGER] = "Fast-charger", + [EXTCON_SLOW_CHARGER] = "Slow-charger", + [EXTCON_CHARGE_DOWNSTREAM] = "Charge-downstream", + [EXTCON_HDMI] = "HDMI", + [EXTCON_MHL] = "MHL", + [EXTCON_DVI] = "DVI", + [EXTCON_VGA] = "VGA", + [EXTCON_DOCK] = "Dock", + [EXTCON_LINE_IN] = "Line-in", + [EXTCON_LINE_OUT] = "Line-out", + [EXTCON_MIC_IN] = "Microphone", + [EXTCON_HEADPHONE_OUT] = "Headphone", + [EXTCON_SPDIF_IN] = "SPDIF-in", + [EXTCON_SPDIF_OUT] = "SPDIF-out", + [EXTCON_VIDEO_IN] = "Video-in", + [EXTCON_VIDEO_OUT] = "Video-out", + [EXTCON_MECHANICAL] = "Mechanical", +}; + +static struct class *extcon_class; +#if defined(CONFIG_ANDROID) +static struct class_compat *switch_class; +#endif /* CONFIG_ANDROID */ + +static LIST_HEAD(extcon_dev_list); +static DEFINE_MUTEX(extcon_dev_list_lock); + +/** + * check_mutually_exclusive - Check if new_state violates mutually_exclusive + * condition. + * @edev: the extcon device + * @new_state: new cable attach status for @edev + * + * Returns 0 if nothing violates. Returns the index + 1 for the first + * violated condition. + */ +static int check_mutually_exclusive(struct extcon_dev *edev, u32 new_state) +{ + int i = 0; + + if (!edev->mutually_exclusive) + return 0; + + for (i = 0; edev->mutually_exclusive[i]; i++) { + int weight; + u32 correspondants = new_state & edev->mutually_exclusive[i]; + + /* calculate the total number of bits set */ + weight = hweight32(correspondants); + if (weight > 1) + return i + 1; + } + + return 0; +} + +static ssize_t state_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int i, count = 0; + struct extcon_dev *edev = dev_get_drvdata(dev); + + if (edev->print_state) { + int ret = edev->print_state(edev, buf); + + if (ret >= 0) + return ret; + /* Use default if failed */ + } + + if (edev->max_supported == 0) + return sprintf(buf, "%u\n", edev->state); + + for (i = 0; i < SUPPORTED_CABLE_MAX; i++) { + if (!edev->supported_cable[i]) + break; + count += sprintf(buf + count, "%s=%d\n", + edev->supported_cable[i], + !!(edev->state & (1 << i))); + } + + return count; +} + +static ssize_t state_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + u32 state; + ssize_t ret = 0; + struct extcon_dev *edev = dev_get_drvdata(dev); + + ret = sscanf(buf, "0x%x", &state); + if (ret == 0) + ret = -EINVAL; + else + ret = extcon_set_state(edev, state); + + if (ret < 0) + return ret; + + return count; +} +static DEVICE_ATTR_RW(state); + +static ssize_t name_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct extcon_dev *edev = dev_get_drvdata(dev); + + /* Optional callback given by the user */ + if (edev->print_name) { + int ret = edev->print_name(edev, buf); + if (ret >= 0) + return ret; + } + + return sprintf(buf, "%s\n", dev_name(&edev->dev)); +} +static DEVICE_ATTR_RO(name); + +static ssize_t cable_name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct extcon_cable *cable = container_of(attr, struct extcon_cable, + attr_name); + + return sprintf(buf, "%s\n", + cable->edev->supported_cable[cable->cable_index]); +} + +static ssize_t cable_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct extcon_cable *cable = container_of(attr, struct extcon_cable, + attr_state); + + return sprintf(buf, "%d\n", + extcon_get_cable_state_(cable->edev, + cable->cable_index)); +} + +/** + * extcon_update_state() - Update the cable attach states of the extcon device + * only for the masked bits. + * @edev: the extcon device + * @mask: the bit mask to designate updated bits. + * @state: new cable attach status for @edev + * + * Changing the state sends uevent with environment variable containing + * the name of extcon device (envp[0]) and the state output (envp[1]). + * Tizen uses this format for extcon device to get events from ports. + * Android uses this format as well. + * + * Note that the notifier provides which bits are changed in the state + * variable with the val parameter (second) to the callback. + */ +int extcon_update_state(struct extcon_dev *edev, u32 mask, u32 state) +{ + char name_buf[120]; + char state_buf[120]; + char *prop_buf; + char *envp[3]; + int env_offset = 0; + int length; + unsigned long flags; + + spin_lock_irqsave(&edev->lock, flags); + + if (edev->state != ((edev->state & ~mask) | (state & mask))) { + u32 old_state = edev->state; + + if (check_mutually_exclusive(edev, (edev->state & ~mask) | + (state & mask))) { + spin_unlock_irqrestore(&edev->lock, flags); + return -EPERM; + } + + edev->state &= ~mask; + edev->state |= state & mask; + + raw_notifier_call_chain(&edev->nh, old_state, edev); + /* This could be in interrupt handler */ + prop_buf = (char *)get_zeroed_page(GFP_ATOMIC); + if (prop_buf) { + length = name_show(&edev->dev, NULL, prop_buf); + if (length > 0) { + if (prop_buf[length - 1] == '\n') + prop_buf[length - 1] = 0; + snprintf(name_buf, sizeof(name_buf), + "NAME=%s", prop_buf); + envp[env_offset++] = name_buf; + } + length = state_show(&edev->dev, NULL, prop_buf); + if (length > 0) { + if (prop_buf[length - 1] == '\n') + prop_buf[length - 1] = 0; + snprintf(state_buf, sizeof(state_buf), + "STATE=%s", prop_buf); + envp[env_offset++] = state_buf; + } + envp[env_offset] = NULL; + /* Unlock early before uevent */ + spin_unlock_irqrestore(&edev->lock, flags); + + kobject_uevent_env(&edev->dev.kobj, KOBJ_CHANGE, envp); + free_page((unsigned long)prop_buf); + } else { + /* Unlock early before uevent */ + spin_unlock_irqrestore(&edev->lock, flags); + + dev_err(&edev->dev, "out of memory in extcon_set_state\n"); + kobject_uevent(&edev->dev.kobj, KOBJ_CHANGE); + } + } else { + /* No changes */ + spin_unlock_irqrestore(&edev->lock, flags); + } + + return 0; +} +EXPORT_SYMBOL_GPL(extcon_update_state); + +/** + * extcon_set_state() - Set the cable attach states of the extcon device. + * @edev: the extcon device + * @state: new cable attach status for @edev + * + * Note that notifier provides which bits are changed in the state + * variable with the val parameter (second) to the callback. + */ +int extcon_set_state(struct extcon_dev *edev, u32 state) +{ + return extcon_update_state(edev, 0xffffffff, state); +} +EXPORT_SYMBOL_GPL(extcon_set_state); + +/** + * extcon_find_cable_index() - Get the cable index based on the cable name. + * @edev: the extcon device that has the cable. + * @cable_name: cable name to be searched. + * + * Note that accessing a cable state based on cable_index is faster than + * cable_name because using cable_name induces a loop with strncmp(). + * Thus, when get/set_cable_state is repeatedly used, using cable_index + * is recommended. + */ +int extcon_find_cable_index(struct extcon_dev *edev, const char *cable_name) +{ + int i; + + if (edev->supported_cable) { + for (i = 0; edev->supported_cable[i]; i++) { + if (!strncmp(edev->supported_cable[i], + cable_name, CABLE_NAME_MAX)) + return i; + } + } + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(extcon_find_cable_index); + +/** + * extcon_get_cable_state_() - Get the status of a specific cable. + * @edev: the extcon device that has the cable. + * @index: cable index that can be retrieved by extcon_find_cable_index(). + */ +int extcon_get_cable_state_(struct extcon_dev *edev, int index) +{ + if (index < 0 || (edev->max_supported && edev->max_supported <= index)) + return -EINVAL; + + return !!(edev->state & (1 << index)); +} +EXPORT_SYMBOL_GPL(extcon_get_cable_state_); + +/** + * extcon_get_cable_state() - Get the status of a specific cable. + * @edev: the extcon device that has the cable. + * @cable_name: cable name. + * + * Note that this is slower than extcon_get_cable_state_. + */ +int extcon_get_cable_state(struct extcon_dev *edev, const char *cable_name) +{ + return extcon_get_cable_state_(edev, extcon_find_cable_index + (edev, cable_name)); +} +EXPORT_SYMBOL_GPL(extcon_get_cable_state); + +/** + * extcon_set_cable_state_() - Set the status of a specific cable. + * @edev: the extcon device that has the cable. + * @index: cable index that can be retrieved by + * extcon_find_cable_index(). + * @cable_state: the new cable status. The default semantics is + * true: attached / false: detached. + */ +int extcon_set_cable_state_(struct extcon_dev *edev, + int index, bool cable_state) +{ + u32 state; + + if (index < 0 || (edev->max_supported && edev->max_supported <= index)) + return -EINVAL; + + state = cable_state ? (1 << index) : 0; + return extcon_update_state(edev, 1 << index, state); +} +EXPORT_SYMBOL_GPL(extcon_set_cable_state_); + +/** + * extcon_set_cable_state() - Set the status of a specific cable. + * @edev: the extcon device that has the cable. + * @cable_name: cable name. + * @cable_state: the new cable status. The default semantics is + * true: attached / false: detached. + * + * Note that this is slower than extcon_set_cable_state_. + */ +int extcon_set_cable_state(struct extcon_dev *edev, + const char *cable_name, bool cable_state) +{ + return extcon_set_cable_state_(edev, extcon_find_cable_index + (edev, cable_name), cable_state); +} +EXPORT_SYMBOL_GPL(extcon_set_cable_state); + +/** + * extcon_get_extcon_dev() - Get the extcon device instance from the name + * @extcon_name: The extcon name provided with extcon_dev_register() + */ +struct extcon_dev *extcon_get_extcon_dev(const char *extcon_name) +{ + struct extcon_dev *sd; + + mutex_lock(&extcon_dev_list_lock); + list_for_each_entry(sd, &extcon_dev_list, entry) { + if (!strcmp(sd->name, extcon_name)) + goto out; + } + sd = NULL; +out: + mutex_unlock(&extcon_dev_list_lock); + return sd; +} +EXPORT_SYMBOL_GPL(extcon_get_extcon_dev); + +static int _call_per_cable(struct notifier_block *nb, unsigned long val, + void *ptr) +{ + struct extcon_specific_cable_nb *obj = container_of(nb, + struct extcon_specific_cable_nb, internal_nb); + struct extcon_dev *edev = ptr; + + if ((val & (1 << obj->cable_index)) != + (edev->state & (1 << obj->cable_index))) { + bool cable_state = true; + + obj->previous_value = val; + + if (val & (1 << obj->cable_index)) + cable_state = false; + + return obj->user_nb->notifier_call(obj->user_nb, + cable_state, ptr); + } + + return NOTIFY_OK; +} + +/** + * extcon_register_interest() - Register a notifier for a state change of a + * specific cable, not an entier set of cables of a + * extcon device. + * @obj: an empty extcon_specific_cable_nb object to be returned. + * @extcon_name: the name of extcon device. + * if NULL, extcon_register_interest will register + * every cable with the target cable_name given. + * @cable_name: the target cable name. + * @nb: the notifier block to get notified. + * + * Provide an empty extcon_specific_cable_nb. extcon_register_interest() sets + * the struct for you. + * + * extcon_register_interest is a helper function for those who want to get + * notification for a single specific cable's status change. If a user wants + * to get notification for any changes of all cables of a extcon device, + * he/she should use the general extcon_register_notifier(). + * + * Note that the second parameter given to the callback of nb (val) is + * "old_state", not the current state. The current state can be retrieved + * by looking at the third pameter (edev pointer)'s state value. + */ +int extcon_register_interest(struct extcon_specific_cable_nb *obj, + const char *extcon_name, const char *cable_name, + struct notifier_block *nb) +{ + if (!obj || !cable_name || !nb) + return -EINVAL; + + if (extcon_name) { + obj->edev = extcon_get_extcon_dev(extcon_name); + if (!obj->edev) + return -ENODEV; + + obj->cable_index = extcon_find_cable_index(obj->edev, + cable_name); + if (obj->cable_index < 0) + return obj->cable_index; + + obj->user_nb = nb; + + obj->internal_nb.notifier_call = _call_per_cable; + + return raw_notifier_chain_register(&obj->edev->nh, + &obj->internal_nb); + } else { + struct class_dev_iter iter; + struct extcon_dev *extd; + struct device *dev; + + if (!extcon_class) + return -ENODEV; + class_dev_iter_init(&iter, extcon_class, NULL, NULL); + while ((dev = class_dev_iter_next(&iter))) { + extd = dev_get_drvdata(dev); + + if (extcon_find_cable_index(extd, cable_name) < 0) + continue; + + class_dev_iter_exit(&iter); + return extcon_register_interest(obj, extd->name, + cable_name, nb); + } + + return -ENODEV; + } +} +EXPORT_SYMBOL_GPL(extcon_register_interest); + +/** + * extcon_unregister_interest() - Unregister the notifier registered by + * extcon_register_interest(). + * @obj: the extcon_specific_cable_nb object returned by + * extcon_register_interest(). + */ +int extcon_unregister_interest(struct extcon_specific_cable_nb *obj) +{ + if (!obj) + return -EINVAL; + + return raw_notifier_chain_unregister(&obj->edev->nh, &obj->internal_nb); +} +EXPORT_SYMBOL_GPL(extcon_unregister_interest); + +/** + * extcon_register_notifier() - Register a notifiee to get notified by + * any attach status changes from the extcon. + * @edev: the extcon device. + * @nb: a notifier block to be registered. + * + * Note that the second parameter given to the callback of nb (val) is + * "old_state", not the current state. The current state can be retrieved + * by looking at the third pameter (edev pointer)'s state value. + */ +int extcon_register_notifier(struct extcon_dev *edev, + struct notifier_block *nb) +{ + return raw_notifier_chain_register(&edev->nh, nb); +} +EXPORT_SYMBOL_GPL(extcon_register_notifier); + +/** + * extcon_unregister_notifier() - Unregister a notifiee from the extcon device. + * @edev: the extcon device. + * @nb: a registered notifier block to be unregistered. + */ +int extcon_unregister_notifier(struct extcon_dev *edev, + struct notifier_block *nb) +{ + return raw_notifier_chain_unregister(&edev->nh, nb); +} +EXPORT_SYMBOL_GPL(extcon_unregister_notifier); + +static struct attribute *extcon_attrs[] = { + &dev_attr_state.attr, + &dev_attr_name.attr, + NULL, +}; +ATTRIBUTE_GROUPS(extcon); + +static int create_extcon_class(void) +{ + if (!extcon_class) { + extcon_class = class_create(THIS_MODULE, "extcon"); + if (IS_ERR(extcon_class)) + return PTR_ERR(extcon_class); + extcon_class->dev_groups = extcon_groups; + +#if defined(CONFIG_ANDROID) + switch_class = class_compat_register("switch"); + if (WARN(!switch_class, "cannot allocate")) + return -ENOMEM; +#endif /* CONFIG_ANDROID */ + } + + return 0; +} + +static void extcon_dev_release(struct device *dev) +{ +} + +static const char *muex_name = "mutually_exclusive"; +static void dummy_sysfs_dev_release(struct device *dev) +{ +} + +/* + * extcon_dev_allocate() - Allocate the memory of extcon device. + * @supported_cable: Array of supported cable names ending with NULL. + * If supported_cable is NULL, cable name related APIs + * are disabled. + * + * This function allocates the memory for extcon device without allocating + * memory in each extcon provider driver and initialize default setting for + * extcon device. + * + * Return the pointer of extcon device if success or ERR_PTR(err) if fail + */ +struct extcon_dev *extcon_dev_allocate(const char **supported_cable) +{ + struct extcon_dev *edev; + + edev = kzalloc(sizeof(*edev), GFP_KERNEL); + if (!edev) + return ERR_PTR(-ENOMEM); + + edev->max_supported = 0; + edev->supported_cable = supported_cable; + + return edev; +} + +/* + * extcon_dev_free() - Free the memory of extcon device. + * @edev: the extcon device to free + */ +void extcon_dev_free(struct extcon_dev *edev) +{ + kfree(edev); +} +EXPORT_SYMBOL_GPL(extcon_dev_free); + +static int devm_extcon_dev_match(struct device *dev, void *res, void *data) +{ + struct extcon_dev **r = res; + + if (WARN_ON(!r || !*r)) + return 0; + + return *r == data; +} + +static void devm_extcon_dev_release(struct device *dev, void *res) +{ + extcon_dev_free(*(struct extcon_dev **)res); +} + +/** + * devm_extcon_dev_allocate - Allocate managed extcon device + * @dev: device owning the extcon device being created + * @supported_cable: Array of supported cable names ending with NULL. + * If supported_cable is NULL, cable name related APIs + * are disabled. + * + * This function manages automatically the memory of extcon device using device + * resource management and simplify the control of freeing the memory of extcon + * device. + * + * Returns the pointer memory of allocated extcon_dev if success + * or ERR_PTR(err) if fail + */ +struct extcon_dev *devm_extcon_dev_allocate(struct device *dev, + const char **supported_cable) +{ + struct extcon_dev **ptr, *edev; + + ptr = devres_alloc(devm_extcon_dev_release, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + + edev = extcon_dev_allocate(supported_cable); + if (IS_ERR(edev)) { + devres_free(ptr); + return edev; + } + + edev->dev.parent = dev; + + *ptr = edev; + devres_add(dev, ptr); + + return edev; +} +EXPORT_SYMBOL_GPL(devm_extcon_dev_allocate); + +void devm_extcon_dev_free(struct device *dev, struct extcon_dev *edev) +{ + WARN_ON(devres_release(dev, devm_extcon_dev_release, + devm_extcon_dev_match, edev)); +} +EXPORT_SYMBOL_GPL(devm_extcon_dev_free); + +/** + * extcon_dev_register() - Register a new extcon device + * @edev : the new extcon device (should be allocated before calling) + * + * Among the members of edev struct, please set the "user initializing data" + * in any case and set the "optional callbacks" if required. However, please + * do not set the values of "internal data", which are initialized by + * this function. + */ +int extcon_dev_register(struct extcon_dev *edev) +{ + int ret, index = 0; + + if (!extcon_class) { + ret = create_extcon_class(); + if (ret < 0) + return ret; + } + + if (edev->supported_cable) { + /* Get size of array */ + for (index = 0; edev->supported_cable[index]; index++) + ; + edev->max_supported = index; + } else { + edev->max_supported = 0; + } + + if (index > SUPPORTED_CABLE_MAX) { + dev_err(&edev->dev, "extcon: maximum number of supported cables exceeded.\n"); + return -EINVAL; + } + + edev->dev.class = extcon_class; + edev->dev.release = extcon_dev_release; + + edev->name = edev->name ? edev->name : dev_name(edev->dev.parent); + if (IS_ERR_OR_NULL(edev->name)) { + dev_err(&edev->dev, + "extcon device name is null\n"); + return -EINVAL; + } + dev_set_name(&edev->dev, "%s", edev->name); + + if (edev->max_supported) { + char buf[10]; + char *str; + struct extcon_cable *cable; + + edev->cables = kzalloc(sizeof(struct extcon_cable) * + edev->max_supported, GFP_KERNEL); + if (!edev->cables) { + ret = -ENOMEM; + goto err_sysfs_alloc; + } + for (index = 0; index < edev->max_supported; index++) { + cable = &edev->cables[index]; + + snprintf(buf, 10, "cable.%d", index); + str = kzalloc(sizeof(char) * (strlen(buf) + 1), + GFP_KERNEL); + if (!str) { + for (index--; index >= 0; index--) { + cable = &edev->cables[index]; + kfree(cable->attr_g.name); + } + ret = -ENOMEM; + + goto err_alloc_cables; + } + strcpy(str, buf); + + cable->edev = edev; + cable->cable_index = index; + cable->attrs[0] = &cable->attr_name.attr; + cable->attrs[1] = &cable->attr_state.attr; + cable->attrs[2] = NULL; + cable->attr_g.name = str; + cable->attr_g.attrs = cable->attrs; + + sysfs_attr_init(&cable->attr_name.attr); + cable->attr_name.attr.name = "name"; + cable->attr_name.attr.mode = 0444; + cable->attr_name.show = cable_name_show; + + sysfs_attr_init(&cable->attr_state.attr); + cable->attr_state.attr.name = "state"; + cable->attr_state.attr.mode = 0444; + cable->attr_state.show = cable_state_show; + } + } + + if (edev->max_supported && edev->mutually_exclusive) { + char buf[80]; + char *name; + + /* Count the size of mutually_exclusive array */ + for (index = 0; edev->mutually_exclusive[index]; index++) + ; + + edev->attrs_muex = kzalloc(sizeof(struct attribute *) * + (index + 1), GFP_KERNEL); + if (!edev->attrs_muex) { + ret = -ENOMEM; + goto err_muex; + } + + edev->d_attrs_muex = kzalloc(sizeof(struct device_attribute) * + index, GFP_KERNEL); + if (!edev->d_attrs_muex) { + ret = -ENOMEM; + kfree(edev->attrs_muex); + goto err_muex; + } + + for (index = 0; edev->mutually_exclusive[index]; index++) { + sprintf(buf, "0x%x", edev->mutually_exclusive[index]); + name = kzalloc(sizeof(char) * (strlen(buf) + 1), + GFP_KERNEL); + if (!name) { + for (index--; index >= 0; index--) { + kfree(edev->d_attrs_muex[index].attr. + name); + } + kfree(edev->d_attrs_muex); + kfree(edev->attrs_muex); + ret = -ENOMEM; + goto err_muex; + } + strcpy(name, buf); + sysfs_attr_init(&edev->d_attrs_muex[index].attr); + edev->d_attrs_muex[index].attr.name = name; + edev->d_attrs_muex[index].attr.mode = 0000; + edev->attrs_muex[index] = &edev->d_attrs_muex[index] + .attr; + } + edev->attr_g_muex.name = muex_name; + edev->attr_g_muex.attrs = edev->attrs_muex; + + } + + if (edev->max_supported) { + edev->extcon_dev_type.groups = + kzalloc(sizeof(struct attribute_group *) * + (edev->max_supported + 2), GFP_KERNEL); + if (!edev->extcon_dev_type.groups) { + ret = -ENOMEM; + goto err_alloc_groups; + } + + edev->extcon_dev_type.name = dev_name(&edev->dev); + edev->extcon_dev_type.release = dummy_sysfs_dev_release; + + for (index = 0; index < edev->max_supported; index++) + edev->extcon_dev_type.groups[index] = + &edev->cables[index].attr_g; + if (edev->mutually_exclusive) + edev->extcon_dev_type.groups[index] = + &edev->attr_g_muex; + + edev->dev.type = &edev->extcon_dev_type; + } + + ret = device_register(&edev->dev); + if (ret) { + put_device(&edev->dev); + goto err_dev; + } +#if defined(CONFIG_ANDROID) + if (switch_class) + ret = class_compat_create_link(switch_class, &edev->dev, NULL); +#endif /* CONFIG_ANDROID */ + + spin_lock_init(&edev->lock); + + RAW_INIT_NOTIFIER_HEAD(&edev->nh); + + dev_set_drvdata(&edev->dev, edev); + edev->state = 0; + + mutex_lock(&extcon_dev_list_lock); + list_add(&edev->entry, &extcon_dev_list); + mutex_unlock(&extcon_dev_list_lock); + + return 0; + +err_dev: + if (edev->max_supported) + kfree(edev->extcon_dev_type.groups); +err_alloc_groups: + if (edev->max_supported && edev->mutually_exclusive) { + for (index = 0; edev->mutually_exclusive[index]; index++) + kfree(edev->d_attrs_muex[index].attr.name); + kfree(edev->d_attrs_muex); + kfree(edev->attrs_muex); + } +err_muex: + for (index = 0; index < edev->max_supported; index++) + kfree(edev->cables[index].attr_g.name); +err_alloc_cables: + if (edev->max_supported) + kfree(edev->cables); +err_sysfs_alloc: + return ret; +} +EXPORT_SYMBOL_GPL(extcon_dev_register); + +/** + * extcon_dev_unregister() - Unregister the extcon device. + * @edev: the extcon device instance to be unregistered. + * + * Note that this does not call kfree(edev) because edev was not allocated + * by this class. + */ +void extcon_dev_unregister(struct extcon_dev *edev) +{ + int index; + + mutex_lock(&extcon_dev_list_lock); + list_del(&edev->entry); + mutex_unlock(&extcon_dev_list_lock); + + if (IS_ERR_OR_NULL(get_device(&edev->dev))) { + dev_err(&edev->dev, "Failed to unregister extcon_dev (%s)\n", + dev_name(&edev->dev)); + return; + } + + device_unregister(&edev->dev); + + if (edev->mutually_exclusive && edev->max_supported) { + for (index = 0; edev->mutually_exclusive[index]; + index++) + kfree(edev->d_attrs_muex[index].attr.name); + kfree(edev->d_attrs_muex); + kfree(edev->attrs_muex); + } + + for (index = 0; index < edev->max_supported; index++) + kfree(edev->cables[index].attr_g.name); + + if (edev->max_supported) { + kfree(edev->extcon_dev_type.groups); + kfree(edev->cables); + } + +#if defined(CONFIG_ANDROID) + if (switch_class) + class_compat_remove_link(switch_class, &edev->dev, NULL); +#endif + put_device(&edev->dev); +} +EXPORT_SYMBOL_GPL(extcon_dev_unregister); + +static void devm_extcon_dev_unreg(struct device *dev, void *res) +{ + extcon_dev_unregister(*(struct extcon_dev **)res); +} + +/** + * devm_extcon_dev_register() - Resource-managed extcon_dev_register() + * @dev: device to allocate extcon device + * @edev: the new extcon device to register + * + * Managed extcon_dev_register() function. If extcon device is attached with + * this function, that extcon device is automatically unregistered on driver + * detach. Internally this function calls extcon_dev_register() function. + * To get more information, refer that function. + * + * If extcon device is registered with this function and the device needs to be + * unregistered separately, devm_extcon_dev_unregister() should be used. + * + * Returns 0 if success or negaive error number if failure. + */ +int devm_extcon_dev_register(struct device *dev, struct extcon_dev *edev) +{ + struct extcon_dev **ptr; + int ret; + + ptr = devres_alloc(devm_extcon_dev_unreg, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return -ENOMEM; + + ret = extcon_dev_register(edev); + if (ret) { + devres_free(ptr); + return ret; + } + + *ptr = edev; + devres_add(dev, ptr); + + return 0; +} +EXPORT_SYMBOL_GPL(devm_extcon_dev_register); + +/** + * devm_extcon_dev_unregister() - Resource-managed extcon_dev_unregister() + * @dev: device the extcon belongs to + * @edev: the extcon device to unregister + * + * Unregister extcon device that is registered with devm_extcon_dev_register() + * function. + */ +void devm_extcon_dev_unregister(struct device *dev, struct extcon_dev *edev) +{ + WARN_ON(devres_release(dev, devm_extcon_dev_unreg, + devm_extcon_dev_match, edev)); +} +EXPORT_SYMBOL_GPL(devm_extcon_dev_unregister); + +#ifdef CONFIG_OF +/* + * extcon_get_edev_by_phandle - Get the extcon device from devicetree + * @dev - instance to the given device + * @index - index into list of extcon_dev + * + * return the instance of extcon device + */ +struct extcon_dev *extcon_get_edev_by_phandle(struct device *dev, int index) +{ + struct device_node *node; + struct extcon_dev *edev; + + if (!dev->of_node) { + dev_err(dev, "device does not have a device node entry\n"); + return ERR_PTR(-EINVAL); + } + + node = of_parse_phandle(dev->of_node, "extcon", index); + if (!node) { + dev_err(dev, "failed to get phandle in %s node\n", + dev->of_node->full_name); + return ERR_PTR(-ENODEV); + } + + mutex_lock(&extcon_dev_list_lock); + list_for_each_entry(edev, &extcon_dev_list, entry) { + if (edev->dev.parent && edev->dev.parent->of_node == node) { + mutex_unlock(&extcon_dev_list_lock); + return edev; + } + } + mutex_unlock(&extcon_dev_list_lock); + + return ERR_PTR(-EPROBE_DEFER); +} +#else +struct extcon_dev *extcon_get_edev_by_phandle(struct device *dev, int index) +{ + return ERR_PTR(-ENOSYS); +} +#endif /* CONFIG_OF */ +EXPORT_SYMBOL_GPL(extcon_get_edev_by_phandle); + +static int __init extcon_class_init(void) +{ + return create_extcon_class(); +} +module_init(extcon_class_init); + +static void __exit extcon_class_exit(void) +{ +#if defined(CONFIG_ANDROID) + class_compat_unregister(switch_class); +#endif + class_destroy(extcon_class); +} +module_exit(extcon_class_exit); + +MODULE_AUTHOR("Mike Lockwood "); +MODULE_AUTHOR("Donggeun Kim "); +MODULE_AUTHOR("MyungJoo Ham "); +MODULE_DESCRIPTION("External connector (extcon) class driver"); +MODULE_LICENSE("GPL"); -- cgit v0.10.2 From 34825e511971e193db16a96350faeb60eff0d842 Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Sat, 7 Mar 2015 01:41:36 +0900 Subject: extcon: Fix the checkpatch warning This patch fixes the checkpatch warning about coding style. Signed-off-by: Chanwoo Choi diff --git a/drivers/extcon/extcon-max14577.c b/drivers/extcon/extcon-max14577.c index c1bf0cf..3823aa4 100644 --- a/drivers/extcon/extcon-max14577.c +++ b/drivers/extcon/extcon-max14577.c @@ -539,8 +539,6 @@ static void max14577_muic_irq_work(struct work_struct *work) dev_err(info->dev, "failed to handle MUIC interrupt\n"); mutex_unlock(&info->mutex); - - return; } /* @@ -730,8 +728,7 @@ static int max14577_muic_probe(struct platform_device *pdev) muic_irq->name, info); if (ret) { dev_err(&pdev->dev, - "failed: irq request (IRQ: %d," - " error :%d)\n", + "failed: irq request (IRQ: %d, error :%d)\n", muic_irq->irq, ret); return ret; } diff --git a/drivers/extcon/extcon-max77693.c b/drivers/extcon/extcon-max77693.c index dfcc5cb..a66bec8 100644 --- a/drivers/extcon/extcon-max77693.c +++ b/drivers/extcon/extcon-max77693.c @@ -1019,8 +1019,6 @@ static void max77693_muic_irq_work(struct work_struct *work) dev_err(info->dev, "failed to handle MUIC interrupt\n"); mutex_unlock(&info->mutex); - - return; } static irqreturn_t max77693_muic_irq_handler(int irq, void *data) @@ -1171,8 +1169,7 @@ static int max77693_muic_probe(struct platform_device *pdev) muic_irq->name, info); if (ret) { dev_err(&pdev->dev, - "failed: irq request (IRQ: %d," - " error :%d)\n", + "failed: irq request (IRQ: %d, error :%d)\n", muic_irq->irq, ret); return ret; } diff --git a/drivers/extcon/extcon-max8997.c b/drivers/extcon/extcon-max8997.c index fc1678f..5774e56 100644 --- a/drivers/extcon/extcon-max8997.c +++ b/drivers/extcon/extcon-max8997.c @@ -579,8 +579,6 @@ static void max8997_muic_irq_work(struct work_struct *work) dev_err(info->dev, "failed to handle MUIC interrupt\n"); mutex_unlock(&info->mutex); - - return; } static irqreturn_t max8997_muic_irq_handler(int irq, void *data) @@ -689,8 +687,7 @@ static int max8997_muic_probe(struct platform_device *pdev) muic_irq->name, info); if (ret) { dev_err(&pdev->dev, - "failed: irq request (IRQ: %d," - " error :%d)\n", + "failed: irq request (IRQ: %d, error :%d)\n", muic_irq->irq, ret); goto err_irq; } diff --git a/drivers/extcon/extcon-rt8973a.c b/drivers/extcon/extcon-rt8973a.c index a784b2d..9ccd5af 100644 --- a/drivers/extcon/extcon-rt8973a.c +++ b/drivers/extcon/extcon-rt8973a.c @@ -582,10 +582,8 @@ static int rt8973a_muic_i2c_probe(struct i2c_client *i2c, return -EINVAL; info = devm_kzalloc(&i2c->dev, sizeof(*info), GFP_KERNEL); - if (!info) { - dev_err(&i2c->dev, "failed to allocate memory\n"); + if (!info) return -ENOMEM; - } i2c_set_clientdata(i2c, info); info->dev = &i2c->dev; @@ -681,7 +679,7 @@ static int rt8973a_muic_i2c_remove(struct i2c_client *i2c) return 0; } -static struct of_device_id rt8973a_dt_match[] = { +static const struct of_device_id rt8973a_dt_match[] = { { .compatible = "richtek,rt8973a-muic" }, { }, }; diff --git a/drivers/extcon/extcon-sm5502.c b/drivers/extcon/extcon-sm5502.c index b0f7bd8..2f93cf3 100644 --- a/drivers/extcon/extcon-sm5502.c +++ b/drivers/extcon/extcon-sm5502.c @@ -359,8 +359,8 @@ static unsigned int sm5502_muic_get_cable_type(struct sm5502_muic_info *info) break; default: dev_dbg(info->dev, - "cannot identify the cable type: adc(0x%x) " - "dev_type1(0x%x)\n", adc, dev_type1); + "cannot identify the cable type: adc(0x%x)\n", + adc); return -EINVAL; }; break; @@ -659,7 +659,7 @@ static int sm5502_muic_i2c_remove(struct i2c_client *i2c) return 0; } -static struct of_device_id sm5502_dt_match[] = { +static const struct of_device_id sm5502_dt_match[] = { { .compatible = "siliconmitus,sm5502-muic" }, { }, }; diff --git a/drivers/extcon/extcon-usb-gpio.c b/drivers/extcon/extcon-usb-gpio.c index 3f0bad3..de67fce 100644 --- a/drivers/extcon/extcon-usb-gpio.c +++ b/drivers/extcon/extcon-usb-gpio.c @@ -214,7 +214,7 @@ static int usb_extcon_resume(struct device *dev) static SIMPLE_DEV_PM_OPS(usb_extcon_pm_ops, usb_extcon_suspend, usb_extcon_resume); -static struct of_device_id usb_extcon_dt_match[] = { +static const struct of_device_id usb_extcon_dt_match[] = { { .compatible = "linux,extcon-usb-gpio", }, { /* sentinel */ } }; diff --git a/drivers/extcon/extcon.c b/drivers/extcon/extcon.c index 8319f25..752ce12 100644 --- a/drivers/extcon/extcon.c +++ b/drivers/extcon/extcon.c @@ -158,6 +158,7 @@ static ssize_t name_show(struct device *dev, struct device_attribute *attr, /* Optional callback given by the user */ if (edev->print_name) { int ret = edev->print_name(edev, buf); + if (ret >= 0) return ret; } -- cgit v0.10.2 From 208d51154c8d7f2c3808b4401132233c5ab21572 Mon Sep 17 00:00:00 2001 From: Valentin Rothberg Date: Wed, 25 Feb 2015 15:15:23 +0100 Subject: checkkconfigsymbols.py: filter reports for tools/ Recent changes to the build system of tools suggest to filter reports for the entire tools directory. Various C preprocessor identifiers are prefixed with CONFIG_ but are NOT defined in Kconfig but in Makefiles in the tools directory. Such identifiers are false positives for most static analysis tools (i.e., scripts/checkkconfigsymbols.py) since the CONFIG_ prefix and the _MODULE suffix is reserved for Kconfig features in CPP and Make syntax. Signed-off-by: Valentin Rothberg Signed-off-by: Greg Kroah-Hartman diff --git a/scripts/checkkconfigsymbols.py b/scripts/checkkconfigsymbols.py old mode 100644 new mode 100755 index e9cc689..6445693 --- a/scripts/checkkconfigsymbols.py +++ b/scripts/checkkconfigsymbols.py @@ -2,7 +2,7 @@ """Find Kconfig identifiers that are referenced but not defined.""" -# (c) 2014 Valentin Rothberg +# (c) 2014-2015 Valentin Rothberg # (c) 2014 Stefan Hengelein # # Licensed under the terms of the GNU GPL License version 2 @@ -46,8 +46,9 @@ def main(): stdout = stdout[:-1] for gitfile in stdout.rsplit("\n"): - if ".git" in gitfile or "ChangeLog" in gitfile or \ - ".log" in gitfile or os.path.isdir(gitfile): + if ".git" in gitfile or "ChangeLog" in gitfile or \ + ".log" in gitfile or os.path.isdir(gitfile) or \ + gitfile.startswith("tools/"): continue if REGEX_FILE_KCONFIG.match(gitfile): kconfig_files.append(gitfile) -- cgit v0.10.2 From d927c59076f505a056e78a9fc9744069aa052cc8 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Sun, 15 Mar 2015 13:49:47 +0300 Subject: extcon: max77843: Fix signedness bug in max77843_muic_set_debounce_time() This patch fixes the variable type of 'ret' from 'unsigned int' to 'int' type because the return type of regmap_update_bits() is 'int' type. Fixes: 27a28d32b4f2 ('extcon: max77843: Add max77843 MUIC driver') Signed-off-by: Dan Carpenter [cw00.choi: Fix the patch description] Signed-off-by: Chanwoo Choi diff --git a/drivers/extcon/extcon-max77843.c b/drivers/extcon/extcon-max77843.c index 598a017..a3f80ca 100644 --- a/drivers/extcon/extcon-max77843.c +++ b/drivers/extcon/extcon-max77843.c @@ -682,7 +682,7 @@ static int max77843_muic_set_debounce_time(struct max77843_muic_info *info, enum max77843_muic_adc_debounce_time time) { struct max77843 *max77843 = info->max77843; - unsigned int ret; + int ret; switch (time) { case MAX77843_DEBOUNCE_TIME_5MS: -- cgit v0.10.2 From 6893d9b51093cf499ee6217e98c50006ec2727c6 Mon Sep 17 00:00:00 2001 From: Fabian Frederick Date: Mon, 16 Mar 2015 20:20:26 +0100 Subject: misc: constify of_device_id array of_device_id is always used as const. (See driver.of_match_table and open firmware functions) Signed-off-by: Fabian Frederick Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/misc/carma/carma-fpga-program.c b/drivers/misc/carma/carma-fpga-program.c index 06166ac..fff8c43 100644 --- a/drivers/misc/carma/carma-fpga-program.c +++ b/drivers/misc/carma/carma-fpga-program.c @@ -1142,7 +1142,7 @@ out_return: return ret; } -static struct of_device_id fpga_of_match[] = { +static const struct of_device_id fpga_of_match[] = { { .compatible = "carma,fpga-programmer", }, {}, }; diff --git a/drivers/misc/carma/carma-fpga.c b/drivers/misc/carma/carma-fpga.c index 68cdfe1..5aba3fd 100644 --- a/drivers/misc/carma/carma-fpga.c +++ b/drivers/misc/carma/carma-fpga.c @@ -1486,7 +1486,7 @@ static int data_of_remove(struct platform_device *op) return 0; } -static struct of_device_id data_of_match[] = { +static const struct of_device_id data_of_match[] = { { .compatible = "carma,carma-fpga", }, {}, }; diff --git a/drivers/misc/lis3lv02d/lis3lv02d_i2c.c b/drivers/misc/lis3lv02d/lis3lv02d_i2c.c index 63fe096..e3e7f1d 100644 --- a/drivers/misc/lis3lv02d/lis3lv02d_i2c.c +++ b/drivers/misc/lis3lv02d/lis3lv02d_i2c.c @@ -106,7 +106,7 @@ static union axis_conversion lis3lv02d_axis_map = { .as_array = { LIS3_DEV_X, LIS3_DEV_Y, LIS3_DEV_Z } }; #ifdef CONFIG_OF -static struct of_device_id lis3lv02d_i2c_dt_ids[] = { +static const struct of_device_id lis3lv02d_i2c_dt_ids[] = { { .compatible = "st,lis3lv02d" }, {} }; diff --git a/drivers/misc/lis3lv02d/lis3lv02d_spi.c b/drivers/misc/lis3lv02d/lis3lv02d_spi.c index bd06d0c..b2f6e16 100644 --- a/drivers/misc/lis3lv02d/lis3lv02d_spi.c +++ b/drivers/misc/lis3lv02d/lis3lv02d_spi.c @@ -61,7 +61,7 @@ static union axis_conversion lis3lv02d_axis_normal = { .as_array = { 1, 2, 3 } }; #ifdef CONFIG_OF -static struct of_device_id lis302dl_spi_dt_ids[] = { +static const struct of_device_id lis302dl_spi_dt_ids[] = { { .compatible = "st,lis302dl-spi" }, {} }; diff --git a/drivers/misc/sram.c b/drivers/misc/sram.c index 21181fa..1ed37ce 100644 --- a/drivers/misc/sram.c +++ b/drivers/misc/sram.c @@ -205,7 +205,7 @@ static int sram_remove(struct platform_device *pdev) } #ifdef CONFIG_OF -static struct of_device_id sram_dt_ids[] = { +static const struct of_device_id sram_dt_ids[] = { { .compatible = "mmio-sram" }, {} }; -- cgit v0.10.2 From da2ff527e44bf3af851c1e5d9ac82d248df35417 Mon Sep 17 00:00:00 2001 From: Fabian Frederick Date: Mon, 16 Mar 2015 20:17:13 +0100 Subject: char: constify of_device_id array of_device_id is always used as const. (See driver.of_match_table and open firmware functions) Signed-off-by: Fabian Frederick Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/char/hw_random/pasemi-rng.c b/drivers/char/hw_random/pasemi-rng.c index 3eb7bdd..51cb1d5 100644 --- a/drivers/char/hw_random/pasemi-rng.c +++ b/drivers/char/hw_random/pasemi-rng.c @@ -133,7 +133,7 @@ static int rng_remove(struct platform_device *dev) return 0; } -static struct of_device_id rng_match[] = { +static const struct of_device_id rng_match[] = { { .compatible = "1682m-rng", }, { .compatible = "pasemi,pwrficient-rng", }, { }, diff --git a/drivers/char/hw_random/powernv-rng.c b/drivers/char/hw_random/powernv-rng.c index 3f4f632..263a5bb 100644 --- a/drivers/char/hw_random/powernv-rng.c +++ b/drivers/char/hw_random/powernv-rng.c @@ -61,7 +61,7 @@ static int powernv_rng_probe(struct platform_device *pdev) return 0; } -static struct of_device_id powernv_rng_match[] = { +static const struct of_device_id powernv_rng_match[] = { { .compatible = "ibm,power-rng",}, {}, }; diff --git a/drivers/char/hw_random/ppc4xx-rng.c b/drivers/char/hw_random/ppc4xx-rng.c index c85d31a..b2cfda0 100644 --- a/drivers/char/hw_random/ppc4xx-rng.c +++ b/drivers/char/hw_random/ppc4xx-rng.c @@ -123,7 +123,7 @@ static int ppc4xx_rng_remove(struct platform_device *dev) return 0; } -static struct of_device_id ppc4xx_rng_match[] = { +static const struct of_device_id ppc4xx_rng_match[] = { { .compatible = "ppc4xx-rng", }, { .compatible = "amcc,ppc460ex-rng", }, { .compatible = "amcc,ppc440epx-rng", }, diff --git a/drivers/char/ipmi/ipmi_si_intf.c b/drivers/char/ipmi/ipmi_si_intf.c index f6646ed..e6a6da4 100644 --- a/drivers/char/ipmi/ipmi_si_intf.c +++ b/drivers/char/ipmi/ipmi_si_intf.c @@ -2664,7 +2664,7 @@ static struct pci_driver ipmi_pci_driver = { }; #endif /* CONFIG_PCI */ -static struct of_device_id ipmi_match[]; +static const struct of_device_id ipmi_match[]; static int ipmi_probe(struct platform_device *dev) { #ifdef CONFIG_OF @@ -2761,7 +2761,7 @@ static int ipmi_remove(struct platform_device *dev) return 0; } -static struct of_device_id ipmi_match[] = +static const struct of_device_id ipmi_match[] = { { .type = "ipmi", .compatible = "ipmi-kcs", .data = (void *)(unsigned long) SI_KCS }, diff --git a/drivers/char/xillybus/xillybus_of.c b/drivers/char/xillybus/xillybus_of.c index 2002a3a..7818650 100644 --- a/drivers/char/xillybus/xillybus_of.c +++ b/drivers/char/xillybus/xillybus_of.c @@ -31,7 +31,7 @@ MODULE_LICENSE("GPL v2"); static const char xillyname[] = "xillybus_of"; /* Match table for of_platform binding */ -static struct of_device_id xillybus_of_match[] = { +static const struct of_device_id xillybus_of_match[] = { { .compatible = "xillybus,xillybus-1.00.a", }, { .compatible = "xlnx,xillybus-1.00.a", }, /* Deprecated */ {} -- cgit v0.10.2 From b1a3f243485fa2643bc75fd70a23bbd7cfc74f2d Mon Sep 17 00:00:00 2001 From: Valentin Rothberg Date: Mon, 16 Mar 2015 12:16:14 +0100 Subject: checkkconfigsymbols.py: make it Git aware The script now supports to check a specified commit or a specified range of commits (i.e., commit1..commit2). Developers and maintainers are encouraged to use this functionality before sending or merging patches to avoid potential bugs and to keep the code, documentation, etc. clean. This patch adds the following options to the script: -c COMMIT, --commit=COMMIT Check if the specified commit (hash) introduces undefined Kconfig symbols. -d DIFF, --diff=DIFF Diff undefined symbols between two commits. The input format bases on Git log's 'commmit1..commit2'. --force Reset current Git tree even when it's dirty. Note that the first two options require to 'git reset --hard' the user's Git tree. This hard reset is necessary to keep the script fast, but it can lead to the loss of uncommitted data. Hence, the script aborts in case it is executed in a dirty tree. It won't abort if '--force' is passed. If neither -c nor -d is specified, the script defaults to check the entire local tree (i.e., the previous behavior). Signed-off-by: Valentin Rothberg Signed-off-by: Greg Kroah-Hartman diff --git a/scripts/checkkconfigsymbols.py b/scripts/checkkconfigsymbols.py index 6445693..ce9ca60 100755 --- a/scripts/checkkconfigsymbols.py +++ b/scripts/checkkconfigsymbols.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -"""Find Kconfig identifiers that are referenced but not defined.""" +"""Find Kconfig symbols that are referenced but not defined.""" # (c) 2014-2015 Valentin Rothberg # (c) 2014 Stefan Hengelein @@ -10,7 +10,9 @@ import os import re +import sys from subprocess import Popen, PIPE, STDOUT +from optparse import OptionParser # regex expressions @@ -32,16 +34,140 @@ REGEX_KCONFIG_HELP = re.compile(r"^\s+(help|---help---)\s*$") REGEX_FILTER_FEATURES = re.compile(r"[A-Za-z0-9]$") +def parse_options(): + """The user interface of this module.""" + usage = "%prog [options]\n\n" \ + "Run this tool to detect Kconfig symbols that are referenced but " \ + "not defined in\nKconfig. The output of this tool has the " \ + "format \'Undefined symbol\\tFile list\'\n\n" \ + "If no option is specified, %prog will default to check your\n" \ + "current tree. Please note that specifying commits will " \ + "\'git reset --hard\'\nyour current tree! You may save " \ + "uncommitted changes to avoid losing data." + + parser = OptionParser(usage=usage) + + parser.add_option('-c', '--commit', dest='commit', action='store', + default="", + help="Check if the specified commit (hash) introduces " + "undefined Kconfig symbols.") + + parser.add_option('-d', '--diff', dest='diff', action='store', + default="", + help="Diff undefined symbols between two commits. The " + "input format bases on Git log's " + "\'commmit1..commit2\'.") + + parser.add_option('', '--force', dest='force', action='store_true', + default=False, + help="Reset current Git tree even when it's dirty.") + + (opts, _) = parser.parse_args() + + if opts.commit and opts.diff: + sys.exit("Please specify only one option at once.") + + if opts.diff and not re.match(r"^[\w\-\.]+\.\.[\w\-\.]+$", opts.diff): + sys.exit("Please specify valid input in the following format: " + "\'commmit1..commit2\'") + + if opts.commit or opts.diff: + if not opts.force and tree_is_dirty(): + sys.exit("The current Git tree is dirty (see 'git status'). " + "Running this script may\ndelete important data since it " + "calls 'git reset --hard' for some performance\nreasons. " + " Please run this script in a clean Git tree or pass " + "'--force' if you\nwant to ignore this warning and " + "continue.") + + return opts + + def main(): """Main function of this module.""" + opts = parse_options() + + if opts.commit or opts.diff: + head = get_head() + + # get commit range + commit_a = None + commit_b = None + if opts.commit: + commit_a = opts.commit + "~" + commit_b = opts.commit + elif opts.diff: + split = opts.diff.split("..") + commit_a = split[0] + commit_b = split[1] + undefined_a = {} + undefined_b = {} + + # get undefined items before the commit + execute("git reset --hard %s" % commit_a) + undefined_a = check_symbols() + + # get undefined items for the commit + execute("git reset --hard %s" % commit_b) + undefined_b = check_symbols() + + # report cases that are present for the commit but not before + for feature in undefined_b: + # feature has not been undefined before + if not feature in undefined_a: + files = undefined_b.get(feature) + print "%s\t%s" % (feature, ", ".join(files)) + # check if there are new files that reference the undefined feature + else: + files = undefined_b.get(feature) - undefined_a.get(feature) + if files: + print "%s\t%s" % (feature, ", ".join(files)) + + # reset to head + execute("git reset --hard %s" % head) + + # default to check the entire tree + else: + undefined = check_symbols() + for feature in undefined: + files = undefined.get(feature) + + +def execute(cmd): + """Execute %cmd and return stdout. Exit in case of error.""" + pop = Popen(cmd, stdout=PIPE, stderr=STDOUT, shell=True) + (stdout, _) = pop.communicate() # wait until finished + if pop.returncode != 0: + sys.exit(stdout) + return stdout + + +def tree_is_dirty(): + """Return true if the current working tree is dirty (i.e., if any file has + been added, deleted, modified, renamed or copied but not committed).""" + stdout = execute("git status --porcelain") + for line in stdout: + if re.findall(r"[URMADC]{1}", line[:2]): + return True + return False + + +def get_head(): + """Return commit hash of current HEAD.""" + stdout = execute("git rev-parse HEAD") + return stdout.strip('\n') + + +def check_symbols(): + """Find undefined Kconfig symbols and return a dict with the symbol as key + and a list of referencing files as value.""" source_files = [] kconfig_files = [] defined_features = set() referenced_features = dict() # {feature: [files]} # use 'git ls-files' to get the worklist - pop = Popen("git ls-files", stdout=PIPE, stderr=STDOUT, shell=True) - (stdout, _) = pop.communicate() # wait until finished + stdout = execute("git ls-files") if len(stdout) > 0 and stdout[-1] == "\n": stdout = stdout[:-1] @@ -62,7 +188,7 @@ def main(): for kfile in kconfig_files: parse_kconfig_file(kfile, defined_features, referenced_features) - print "Undefined symbol used\tFile list" + undefined = {} # {feature: [files]} for feature in sorted(referenced_features): # filter some false positives if feature == "FOO" or feature == "BAR" or \ @@ -73,8 +199,8 @@ def main(): # avoid false positives for kernel modules if feature[:-len("_MODULE")] in defined_features: continue - files = referenced_features.get(feature) - print "%s\t%s" % (feature, ", ".join(files)) + undefined[feature] = referenced_features.get(feature) + return undefined def parse_source_file(sfile, referenced_features): -- cgit v0.10.2 From be7bee9f80c1237e70cc6411e5b432681941247c Mon Sep 17 00:00:00 2001 From: Jean Delvare Date: Sun, 8 Mar 2015 09:49:50 +0100 Subject: misc: bh1780: Add module aliases The bh1780gli driver does not create an i2c module alias for the device it supports, preventing the driver from being loaded automatically when needed on non-OF/DT systems. Add it. Signed-off-by: Jean Delvare Cc: Arnd Bergmann Cc: Greg Kroah-Hartman Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/misc/bh1780gli.c b/drivers/misc/bh1780gli.c index 4c4a59b..7f90ce5 100644 --- a/drivers/misc/bh1780gli.c +++ b/drivers/misc/bh1780gli.c @@ -230,6 +230,8 @@ static const struct i2c_device_id bh1780_id[] = { { }, }; +MODULE_DEVICE_TABLE(i2c, bh1780_id); + #ifdef CONFIG_OF static const struct of_device_id of_bh1780_match[] = { { .compatible = "rohm,bh1780gli", }, -- cgit v0.10.2 From 71a49d16f06de2ccdf52ca247d496a2bb1ca36fe Mon Sep 17 00:00:00 2001 From: Abhilash Kesavan Date: Fri, 6 Feb 2015 19:15:26 +0530 Subject: m32r: add definition of ioremap_wc to io.h Before adding a resource managed ioremap_wc function, we need to have ioremap_wc defined for m32r to prevent build errors. Signed-off-by: Abhilash Kesavan Acked-by: Catalin Marinas Signed-off-by: Greg Kroah-Hartman diff --git a/arch/m32r/include/asm/io.h b/arch/m32r/include/asm/io.h index 6e7787f..9cc00db 100644 --- a/arch/m32r/include/asm/io.h +++ b/arch/m32r/include/asm/io.h @@ -67,6 +67,7 @@ static inline void __iomem *ioremap(unsigned long offset, unsigned long size) extern void iounmap(volatile void __iomem *addr); #define ioremap_nocache(off,size) ioremap(off,size) +#define ioremap_wc ioremap_nocache /* * IO bus memory addresses are also 1:1 with the physical address -- cgit v0.10.2 From 34644524bce91883d5051a7eaf3ec5464ed149bf Mon Sep 17 00:00:00 2001 From: Abhilash Kesavan Date: Fri, 6 Feb 2015 19:15:27 +0530 Subject: lib: devres: add a helper function for ioremap_wc Implement a resource managed writecombine ioremap function. Signed-off-by: Abhilash Kesavan Acked-by: Catalin Marinas Signed-off-by: Greg Kroah-Hartman diff --git a/Documentation/driver-model/devres.txt b/Documentation/driver-model/devres.txt index 6d1e8ee..7fe7fd2 100644 --- a/Documentation/driver-model/devres.txt +++ b/Documentation/driver-model/devres.txt @@ -276,6 +276,7 @@ IOMAP devm_ioport_unmap() devm_ioremap() devm_ioremap_nocache() + devm_ioremap_wc() devm_ioremap_resource() : checks resource, requests memory region, ioremaps devm_iounmap() pcim_iomap() diff --git a/include/linux/io.h b/include/linux/io.h index fa02e55..42b33f0 100644 --- a/include/linux/io.h +++ b/include/linux/io.h @@ -64,6 +64,8 @@ void __iomem *devm_ioremap(struct device *dev, resource_size_t offset, resource_size_t size); void __iomem *devm_ioremap_nocache(struct device *dev, resource_size_t offset, resource_size_t size); +void __iomem *devm_ioremap_wc(struct device *dev, resource_size_t offset, + resource_size_t size); void devm_iounmap(struct device *dev, void __iomem *addr); int check_signature(const volatile void __iomem *io_addr, const unsigned char *signature, int length); diff --git a/lib/devres.c b/lib/devres.c index 0f1dd2e..fbe2aac 100644 --- a/lib/devres.c +++ b/lib/devres.c @@ -72,6 +72,34 @@ void __iomem *devm_ioremap_nocache(struct device *dev, resource_size_t offset, EXPORT_SYMBOL(devm_ioremap_nocache); /** + * devm_ioremap_wc - Managed ioremap_wc() + * @dev: Generic device to remap IO address for + * @offset: BUS offset to map + * @size: Size of map + * + * Managed ioremap_wc(). Map is automatically unmapped on driver detach. + */ +void __iomem *devm_ioremap_wc(struct device *dev, resource_size_t offset, + resource_size_t size) +{ + void __iomem **ptr, *addr; + + ptr = devres_alloc(devm_ioremap_release, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return NULL; + + addr = ioremap_wc(offset, size); + if (addr) { + *ptr = addr; + devres_add(dev, ptr); + } else + devres_free(ptr); + + return addr; +} +EXPORT_SYMBOL(devm_ioremap_wc); + +/** * devm_iounmap - Managed iounmap() * @dev: Generic device to unmap for * @addr: Address to unmap -- cgit v0.10.2 From 0ab163ad1ea0bb0ccd4ada2a54834041611d76f1 Mon Sep 17 00:00:00 2001 From: Abhilash Kesavan Date: Fri, 6 Feb 2015 19:15:28 +0530 Subject: misc: sram: switch to ioremap_wc from ioremap Currently, the SRAM allocator returns device memory via ioremap. This causes issues on ARM64 when the internal SoC SRAM allocated by the generic sram driver is used for audio playback. The destination buffer address (which is ioremapped SRAM) is not 64-bit aligned for certain streams (e.g. 44.1k sampling rate). In such cases we get unhandled alignment faults. Use ioremap_wc in place of ioremap which gives us normal non-cacheable memory instead of device memory. Signed-off-by: Abhilash Kesavan Tested-by: Tony Lindgren Tested-by: Heiko Stuebner Acked-by: Catalin Marinas Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/misc/sram.c b/drivers/misc/sram.c index 1ed37ce..eeaaf5f 100644 --- a/drivers/misc/sram.c +++ b/drivers/misc/sram.c @@ -69,12 +69,23 @@ static int sram_probe(struct platform_device *pdev) INIT_LIST_HEAD(&reserve_list); res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - virt_base = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(virt_base)) - return PTR_ERR(virt_base); + if (!res) { + dev_err(&pdev->dev, "found no memory resource\n"); + return -EINVAL; + } size = resource_size(res); + if (!devm_request_mem_region(&pdev->dev, + res->start, size, pdev->name)) { + dev_err(&pdev->dev, "could not request region for resource\n"); + return -EBUSY; + } + + virt_base = devm_ioremap_wc(&pdev->dev, res->start, size); + if (IS_ERR(virt_base)) + return PTR_ERR(virt_base); + sram = devm_kzalloc(&pdev->dev, sizeof(*sram), GFP_KERNEL); if (!sram) return -ENOMEM; -- cgit v0.10.2 From dae6cdabe45baecfcfd02fbf11d97bb645450fc6 Mon Sep 17 00:00:00 2001 From: Vaishali Thakkar Date: Wed, 11 Feb 2015 16:25:38 +0530 Subject: pcmcia: Use setup_timer This patch introduces the use of function setup_timer. This is done using Coccinelle and semantic patch used is as follows: @@ expression x,y,z; @@ - init_timer (&x); + setup_timer (&x, y, z); - x.function = y; - x.data = z; Signed-off-by: Vaishali Thakkar Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/pcmcia/omap_cf.c b/drivers/pcmcia/omap_cf.c index 8170102..4e2f501 100644 --- a/drivers/pcmcia/omap_cf.c +++ b/drivers/pcmcia/omap_cf.c @@ -220,9 +220,7 @@ static int __init omap_cf_probe(struct platform_device *pdev) cf = kzalloc(sizeof *cf, GFP_KERNEL); if (!cf) return -ENOMEM; - init_timer(&cf->timer); - cf->timer.function = omap_cf_timer; - cf->timer.data = (unsigned long) cf; + setup_timer(&cf->timer, omap_cf_timer, (unsigned long)cf); cf->pdev = pdev; platform_set_drvdata(pdev, cf); diff --git a/drivers/pcmcia/soc_common.c b/drivers/pcmcia/soc_common.c index 933f465..eed5e9c 100644 --- a/drivers/pcmcia/soc_common.c +++ b/drivers/pcmcia/soc_common.c @@ -726,9 +726,8 @@ int soc_pcmcia_add_one(struct soc_pcmcia_socket *skt) { int ret; - init_timer(&skt->poll_timer); - skt->poll_timer.function = soc_common_pcmcia_poll_event; - skt->poll_timer.data = (unsigned long)skt; + setup_timer(&skt->poll_timer, soc_common_pcmcia_poll_event, + (unsigned long)skt); skt->poll_timer.expires = jiffies + SOC_PCMCIA_POLL_PERIOD; ret = request_resource(&iomem_resource, &skt->res_skt); -- cgit v0.10.2 From 03b225b16d75bccf9bc4d82607964a08148adf61 Mon Sep 17 00:00:00 2001 From: Vaishali Thakkar Date: Wed, 11 Feb 2015 16:15:31 +0530 Subject: pcmcia: Use setup_timer and mod_timer This patch introduces the use of functions setup_timer and mod_timer. This is done using Coccinelle and semantic patch used for this as follows: // @@ expression x,y,z,a,b; @@ -init_timer (&x); +setup_timer (&x, y, z); +mod_timer (&a, b); -x.function = y; -x.data = z; -x.expires = b; -add_timer(&a); // Signed-off-by: Vaishali Thakkar Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/pcmcia/pd6729.c b/drivers/pcmcia/pd6729.c index 34ace48..0f70b4d 100644 --- a/drivers/pcmcia/pd6729.c +++ b/drivers/pcmcia/pd6729.c @@ -707,11 +707,9 @@ static int pd6729_pci_probe(struct pci_dev *dev, } } else { /* poll Card status change */ - init_timer(&socket->poll_timer); - socket->poll_timer.function = pd6729_interrupt_wrapper; - socket->poll_timer.data = (unsigned long)socket; - socket->poll_timer.expires = jiffies + HZ; - add_timer(&socket->poll_timer); + setup_timer(&socket->poll_timer, pd6729_interrupt_wrapper, + (unsigned long)socket); + mod_timer(&socket->poll_timer, jiffies + HZ); } for (i = 0; i < MAX_SOCKETS; i++) { diff --git a/drivers/pcmcia/yenta_socket.c b/drivers/pcmcia/yenta_socket.c index 8a23ccb..965bd84 100644 --- a/drivers/pcmcia/yenta_socket.c +++ b/drivers/pcmcia/yenta_socket.c @@ -1236,11 +1236,9 @@ static int yenta_probe(struct pci_dev *dev, const struct pci_device_id *id) if (!socket->cb_irq || request_irq(socket->cb_irq, yenta_interrupt, IRQF_SHARED, "yenta", socket)) { /* No IRQ or request_irq failed. Poll */ socket->cb_irq = 0; /* But zero is a valid IRQ number. */ - init_timer(&socket->poll_timer); - socket->poll_timer.function = yenta_interrupt_wrapper; - socket->poll_timer.data = (unsigned long)socket; - socket->poll_timer.expires = jiffies + HZ; - add_timer(&socket->poll_timer); + setup_timer(&socket->poll_timer, yenta_interrupt_wrapper, + (unsigned long)socket); + mod_timer(&socket->poll_timer, jiffies + HZ); dev_printk(KERN_INFO, &dev->dev, "no PCI IRQ, CardBus support disabled for this " "socket.\n"); -- cgit v0.10.2 From a087146c72bad795bcab80e5987c5b80fa225000 Mon Sep 17 00:00:00 2001 From: Brian Russell Date: Thu, 19 Mar 2015 17:55:26 +0000 Subject: uio: Request/free irq separate from dev lifecycle Separate irq request/free from the device lifecycle. After device unregister the parent module can call pci_disable_msi. >From the PCI MSI how to: "Before calling this function, a device driver must always call free_irq() on any interrupt for which it previously called request_irq(). Failure to do so results in a BUG_ON(), leaving the device with MSI enabled and thus leaking its vector." So we need to separately free the irq at unregister to allow the device to be kept around in the case of it still having open FDs. Signed-off-by: Brian Russell Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/uio/uio.c b/drivers/uio/uio.c index 6276f13..65bf067 100644 --- a/drivers/uio/uio.c +++ b/drivers/uio/uio.c @@ -835,7 +835,15 @@ int __uio_register_device(struct module *owner, info->uio_dev = idev; if (info->irq && (info->irq != UIO_IRQ_CUSTOM)) { - ret = devm_request_irq(idev->dev, info->irq, uio_interrupt, + /* + * Note that we deliberately don't use devm_request_irq + * here. The parent module can unregister the UIO device + * and call pci_disable_msi, which requires that this + * irq has been freed. However, the device may have open + * FDs at the time of unregister and therefore may not be + * freed until they are released. + */ + ret = request_irq(info->irq, uio_interrupt, info->irq_flags, info->name, idev); if (ret) goto err_request_irq; @@ -871,6 +879,8 @@ void uio_unregister_device(struct uio_info *info) uio_dev_del_attributes(idev); + free_irq(idev->info->irq, idev); + device_destroy(&uio_class, MKDEV(uio_major, idev->minor)); return; -- cgit v0.10.2 From b9b518f53996b67833b42ca7751b2085b0dc565a Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Sun, 15 Mar 2015 13:56:04 +0300 Subject: extcon: max77843: Fix an error code in max77843_init_muic_regmap() The i2c_new_dummy() return the NULL if error happen. So, If i2c_new_dummy() return NULL, max77843_init_muic_regmap() return the proper error value (-ENOMEM); Signed-off-by: Dan Carpenter [cw00.choi: Use -ENOMEM instead of -ENODEV and modify patch description] Signed-off-by: Chanwoo Choi diff --git a/drivers/extcon/extcon-max77843.c b/drivers/extcon/extcon-max77843.c index a3f80ca..8db6a92 100644 --- a/drivers/extcon/extcon-max77843.c +++ b/drivers/extcon/extcon-max77843.c @@ -715,7 +715,7 @@ static int max77843_init_muic_regmap(struct max77843 *max77843) if (!max77843->i2c_muic) { dev_err(&max77843->i2c->dev, "Cannot allocate I2C device for MUIC\n"); - return PTR_ERR(max77843->i2c_muic); + return -ENOMEM; } i2c_set_clientdata(max77843->i2c_muic, max77843); -- cgit v0.10.2 From 66bee35f29683fc4a9a530a1c56a0ec45e3f7d72 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 21 Mar 2015 17:26:24 +0100 Subject: extcon: Fix missing locking when [un]registering notifiers Since extcon.c is using raw_notifiers it must protect the notifier list itself when [un]registering notifiers to avoid the list changing while extcon_update_state is walking the list (through raw_notifier_call_chain). Signed-off-by: Hans de Goede [cw00.choi: Apply this patch to extcon.c driver instead of old extcon-class.c] Signed-off-by: Chanwoo Choi diff --git a/drivers/extcon/extcon.c b/drivers/extcon/extcon.c index 752ce12..4c9f165 100644 --- a/drivers/extcon/extcon.c +++ b/drivers/extcon/extcon.c @@ -445,6 +445,9 @@ int extcon_register_interest(struct extcon_specific_cable_nb *obj, const char *extcon_name, const char *cable_name, struct notifier_block *nb) { + unsigned long flags; + int ret; + if (!obj || !cable_name || !nb) return -EINVAL; @@ -462,8 +465,11 @@ int extcon_register_interest(struct extcon_specific_cable_nb *obj, obj->internal_nb.notifier_call = _call_per_cable; - return raw_notifier_chain_register(&obj->edev->nh, + spin_lock_irqsave(&obj->edev->lock, flags); + ret = raw_notifier_chain_register(&obj->edev->nh, &obj->internal_nb); + spin_unlock_irqrestore(&obj->edev->lock, flags); + return ret; } else { struct class_dev_iter iter; struct extcon_dev *extd; @@ -496,10 +502,17 @@ EXPORT_SYMBOL_GPL(extcon_register_interest); */ int extcon_unregister_interest(struct extcon_specific_cable_nb *obj) { + unsigned long flags; + int ret; + if (!obj) return -EINVAL; - return raw_notifier_chain_unregister(&obj->edev->nh, &obj->internal_nb); + spin_lock_irqsave(&obj->edev->lock, flags); + ret = raw_notifier_chain_unregister(&obj->edev->nh, &obj->internal_nb); + spin_unlock_irqrestore(&obj->edev->lock, flags); + + return ret; } EXPORT_SYMBOL_GPL(extcon_unregister_interest); @@ -516,7 +529,14 @@ EXPORT_SYMBOL_GPL(extcon_unregister_interest); int extcon_register_notifier(struct extcon_dev *edev, struct notifier_block *nb) { - return raw_notifier_chain_register(&edev->nh, nb); + unsigned long flags; + int ret; + + spin_lock_irqsave(&edev->lock, flags); + ret = raw_notifier_chain_register(&edev->nh, nb); + spin_unlock_irqrestore(&edev->lock, flags); + + return ret; } EXPORT_SYMBOL_GPL(extcon_register_notifier); @@ -528,7 +548,14 @@ EXPORT_SYMBOL_GPL(extcon_register_notifier); int extcon_unregister_notifier(struct extcon_dev *edev, struct notifier_block *nb) { - return raw_notifier_chain_unregister(&edev->nh, nb); + unsigned long flags; + int ret; + + spin_lock_irqsave(&edev->lock, flags); + ret = raw_notifier_chain_unregister(&edev->nh, nb); + spin_unlock_irqrestore(&edev->lock, flags); + + return ret; } EXPORT_SYMBOL_GPL(extcon_unregister_notifier); -- cgit v0.10.2 From e9533ae539d5cc3d5fc501529d2df7b77e20449c Mon Sep 17 00:00:00 2001 From: Valentin Rothberg Date: Mon, 23 Mar 2015 18:40:49 +0100 Subject: checkkconfigsymbols.py: fix sorted output Commit b1a3f243485f ("checkkconfigsymbols.py: make it Git aware") mistakenly removed to print undefined Kconfig symbols in alphabetical order. Furthermore, the script does not print anything anymore when the entire tree is checked (i.e., when no commit is specified). This patch restores the sorted output and adds the missing print for the default case. Additionally, the file lists are now sorted as well which (a) makes it easier to read and (b) makes the output deterministic. Signed-off-by: Valentin Rothberg Signed-off-by: Greg Kroah-Hartman diff --git a/scripts/checkkconfigsymbols.py b/scripts/checkkconfigsymbols.py index ce9ca60..74086a5 100755 --- a/scripts/checkkconfigsymbols.py +++ b/scripts/checkkconfigsymbols.py @@ -112,14 +112,15 @@ def main(): undefined_b = check_symbols() # report cases that are present for the commit but not before - for feature in undefined_b: + for feature in sorted(undefined_b): # feature has not been undefined before if not feature in undefined_a: - files = undefined_b.get(feature) + files = sorted(undefined_b.get(feature)) print "%s\t%s" % (feature, ", ".join(files)) # check if there are new files that reference the undefined feature else: - files = undefined_b.get(feature) - undefined_a.get(feature) + files = sorted(undefined_b.get(feature) - + undefined_a.get(feature)) if files: print "%s\t%s" % (feature, ", ".join(files)) @@ -129,8 +130,9 @@ def main(): # default to check the entire tree else: undefined = check_symbols() - for feature in undefined: - files = undefined.get(feature) + for feature in sorted(undefined): + files = sorted(undefined.get(feature)) + print "%s\t%s" % (feature, ", ".join(files)) def execute(cmd): -- cgit v0.10.2 From bf1361211dd842659b5b882390de687426f3471a Mon Sep 17 00:00:00 2001 From: Davidlohr Bueso Date: Tue, 17 Feb 2015 14:29:21 -0800 Subject: drivers/vmw_vmci: Show correct get_user_pages_fast upon failure As of 240ddd495a9 (vmw_vmci: Convert driver to use get_user_pages_fast()) we no longer user get_user_pages(), thus update the warning. Also convert to pr_debug, which is a more appropriate level of logging. Signed-off-by: Davidlohr Bueso Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/misc/vmw_vmci/vmci_queue_pair.c b/drivers/misc/vmw_vmci/vmci_queue_pair.c index 35f19a6..c384945 100644 --- a/drivers/misc/vmw_vmci/vmci_queue_pair.c +++ b/drivers/misc/vmw_vmci/vmci_queue_pair.c @@ -737,7 +737,8 @@ static int qp_host_get_user_memory(u64 produce_uva, produce_q->kernel_if->num_pages, 1, produce_q->kernel_if->u.h.header_page); if (retval < produce_q->kernel_if->num_pages) { - pr_warn("get_user_pages(produce) failed (retval=%d)", retval); + pr_debug("get_user_pages_fast(produce) failed (retval=%d)", + retval); qp_release_pages(produce_q->kernel_if->u.h.header_page, retval, false); err = VMCI_ERROR_NO_MEM; @@ -748,7 +749,8 @@ static int qp_host_get_user_memory(u64 produce_uva, consume_q->kernel_if->num_pages, 1, consume_q->kernel_if->u.h.header_page); if (retval < consume_q->kernel_if->num_pages) { - pr_warn("get_user_pages(consume) failed (retval=%d)", retval); + pr_debug("get_user_pages_fast(consume) failed (retval=%d)", + retval); qp_release_pages(consume_q->kernel_if->u.h.header_page, retval, false); qp_release_pages(produce_q->kernel_if->u.h.header_page, -- cgit v0.10.2 From 74b5c297f5ecbef0ca128fa7b385b43f036a7984 Mon Sep 17 00:00:00 2001 From: Andy King Date: Thu, 19 Feb 2015 10:33:56 -0800 Subject: VMCI: Check userland-provided datagram size Ensure that the size filled in by userland in the datagram header matches the size of the buffer passed down in the IOCTL. Note that we account for the size of the header itself in the check. Acked-by: Jorgen Hansen Acked-by: Aditya Sarwade Signed-off-by: Andy King Reported-by: David Ramos Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/misc/vmw_vmci/vmci_driver.c b/drivers/misc/vmw_vmci/vmci_driver.c index 032d35c..cf264a1 100644 --- a/drivers/misc/vmw_vmci/vmci_driver.c +++ b/drivers/misc/vmw_vmci/vmci_driver.c @@ -113,5 +113,5 @@ module_exit(vmci_drv_exit); MODULE_AUTHOR("VMware, Inc."); MODULE_DESCRIPTION("VMware Virtual Machine Communication Interface."); -MODULE_VERSION("1.1.1.0-k"); +MODULE_VERSION("1.1.2.0-k"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/misc/vmw_vmci/vmci_host.c b/drivers/misc/vmw_vmci/vmci_host.c index 66fc992..a721b5d 100644 --- a/drivers/misc/vmw_vmci/vmci_host.c +++ b/drivers/misc/vmw_vmci/vmci_host.c @@ -395,6 +395,12 @@ static int vmci_host_do_send_datagram(struct vmci_host_dev *vmci_host_dev, return -EFAULT; } + if (VMCI_DG_SIZE(dg) != send_info.len) { + vmci_ioctl_err("datagram size mismatch\n"); + kfree(dg); + return -EINVAL; + } + pr_devel("Datagram dst (handle=0x%x:0x%x) src (handle=0x%x:0x%x), payload (size=%llu bytes)\n", dg->dst.context, dg->dst.resource, dg->src.context, dg->src.resource, -- cgit v0.10.2 From aa6467f190d32b263f7144239c89c63c922d9ff8 Mon Sep 17 00:00:00 2001 From: Jorgen Hansen Date: Mon, 2 Mar 2015 08:19:11 -0800 Subject: VMCI: Guard against overflow in queue pair allocation The current maximum size of a queue in a queue pair is 128 MB. If we increase that in the future, the queue pair allocation routines may run into overflow issues. This change adds additional checks to guard against this. Acked-by: Andy King Reported-by: Dan Carpenter Signed-off-by: Jorgen Hansen Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/misc/vmw_vmci/vmci_driver.c b/drivers/misc/vmw_vmci/vmci_driver.c index cf264a1..b823f9a 100644 --- a/drivers/misc/vmw_vmci/vmci_driver.c +++ b/drivers/misc/vmw_vmci/vmci_driver.c @@ -113,5 +113,5 @@ module_exit(vmci_drv_exit); MODULE_AUTHOR("VMware, Inc."); MODULE_DESCRIPTION("VMware Virtual Machine Communication Interface."); -MODULE_VERSION("1.1.2.0-k"); +MODULE_VERSION("1.1.3.0-k"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/misc/vmw_vmci/vmci_queue_pair.c b/drivers/misc/vmw_vmci/vmci_queue_pair.c index c384945..f42d9c4 100644 --- a/drivers/misc/vmw_vmci/vmci_queue_pair.c +++ b/drivers/misc/vmw_vmci/vmci_queue_pair.c @@ -295,12 +295,20 @@ static void *qp_alloc_queue(u64 size, u32 flags) { u64 i; struct vmci_queue *queue; - const size_t num_pages = DIV_ROUND_UP(size, PAGE_SIZE) + 1; - const size_t pas_size = num_pages * sizeof(*queue->kernel_if->u.g.pas); - const size_t vas_size = num_pages * sizeof(*queue->kernel_if->u.g.vas); - const size_t queue_size = - sizeof(*queue) + sizeof(*queue->kernel_if) + - pas_size + vas_size; + size_t pas_size; + size_t vas_size; + size_t queue_size = sizeof(*queue) + sizeof(*queue->kernel_if); + const u64 num_pages = DIV_ROUND_UP(size, PAGE_SIZE) + 1; + + if (num_pages > + (SIZE_MAX - queue_size) / + (sizeof(*queue->kernel_if->u.g.pas) + + sizeof(*queue->kernel_if->u.g.vas))) + return NULL; + + pas_size = num_pages * sizeof(*queue->kernel_if->u.g.pas); + vas_size = num_pages * sizeof(*queue->kernel_if->u.g.vas); + queue_size += pas_size + vas_size; queue = vmalloc(queue_size); if (!queue) @@ -615,10 +623,15 @@ static int qp_memcpy_from_queue_iov(void *dest, static struct vmci_queue *qp_host_alloc_queue(u64 size) { struct vmci_queue *queue; - const size_t num_pages = DIV_ROUND_UP(size, PAGE_SIZE) + 1; + size_t queue_page_size; + const u64 num_pages = DIV_ROUND_UP(size, PAGE_SIZE) + 1; const size_t queue_size = sizeof(*queue) + sizeof(*(queue->kernel_if)); - const size_t queue_page_size = - num_pages * sizeof(*queue->kernel_if->u.h.page); + + if (num_pages > (SIZE_MAX - queue_size) / + sizeof(*queue->kernel_if->u.h.page)) + return NULL; + + queue_page_size = num_pages * sizeof(*queue->kernel_if->u.h.page); queue = kzalloc(queue_size + queue_page_size, GFP_KERNEL); if (queue) { -- cgit v0.10.2 From ab3ae0096a6d31e1b244c5c5155f48ef3700329e Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Thu, 19 Mar 2015 12:10:09 +0200 Subject: mei: fix regression on NFC connection In mei_host_client_init function we enable the all internal connected clients including NFC. This is done before we set the device to enabled state and let userspace call open. We need to check only for MEI_FILE_CONNECTED in mei_cl_is_connected in order to enable the communication with the clients before MEI_DEV_ENABLED is set. Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/misc/mei/client.h b/drivers/misc/mei/client.h index eb02f34..7800d1b 100644 --- a/drivers/misc/mei/client.h +++ b/drivers/misc/mei/client.h @@ -92,9 +92,7 @@ int mei_cl_flow_ctrl_reduce(struct mei_cl *cl); */ static inline bool mei_cl_is_connected(struct mei_cl *cl) { - return cl->dev && - cl->dev->dev_state == MEI_DEV_ENABLED && - cl->state == MEI_FILE_CONNECTED; + return cl->state == MEI_FILE_CONNECTED; } static inline bool mei_cl_is_transitioning(struct mei_cl *cl) { -- cgit v0.10.2 From 03190c67ff72b5c56b24266762ab8abe68970f45 Mon Sep 17 00:00:00 2001 From: Martin Kepplinger Date: Mon, 23 Mar 2015 13:59:46 +0100 Subject: char: misc: document behaviour of open() an open syscall now assignes file->private_data to a pointer to the miscdevice structure. This reminds people not to duplicate code if they want this and not to depend on it being NULL. Signed-off-by: Martin Kepplinger Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/char/misc.c b/drivers/char/misc.c index ffa97d2..c892c29 100644 --- a/drivers/char/misc.c +++ b/drivers/char/misc.c @@ -169,7 +169,9 @@ static const struct file_operations misc_fops = { * the minor number requested is used. * * The structure passed is linked into the kernel and may not be - * destroyed until it has been unregistered. + * destroyed until it has been unregistered. By default, an open() + * syscall to the device sets file->private_data to point to the + * structure. Drivers don't need open in fops for this. * * A zero is returned on success and a negative errno code for * failure. -- cgit v0.10.2 From a819a228feae6f936e2696c4946c53f883a3d480 Mon Sep 17 00:00:00 2001 From: Nicholas Mc Guire Date: Mon, 9 Feb 2015 14:43:44 -0500 Subject: misc: tifm: match return type of wait_for_completion_timeout return type of wait_for_completion_timeout is unsigned long not int. The rc variable is in use for other calls so an additional timeout variable of type unsigned long is added. Signed-off-by: Nicholas Mc Guire Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/misc/tifm_7xx1.c b/drivers/misc/tifm_7xx1.c index a606c89..a37a42f 100644 --- a/drivers/misc/tifm_7xx1.c +++ b/drivers/misc/tifm_7xx1.c @@ -236,6 +236,7 @@ static int tifm_7xx1_resume(struct pci_dev *dev) { struct tifm_adapter *fm = pci_get_drvdata(dev); int rc; + unsigned long timeout; unsigned int good_sockets = 0, bad_sockets = 0; unsigned long flags; unsigned char new_ids[fm->num_sockets]; @@ -272,8 +273,8 @@ static int tifm_7xx1_resume(struct pci_dev *dev) if (good_sockets) { fm->finish_me = &finish_resume; spin_unlock_irqrestore(&fm->lock, flags); - rc = wait_for_completion_timeout(&finish_resume, HZ); - dev_dbg(&dev->dev, "wait returned %d\n", rc); + timeout = wait_for_completion_timeout(&finish_resume, HZ); + dev_dbg(&dev->dev, "wait returned %lu\n", timeout); writel(TIFM_IRQ_FIFOMASK(good_sockets) | TIFM_IRQ_CARDMASK(good_sockets), fm->addr + FM_CLEAR_INTERRUPT_ENABLE); -- cgit v0.10.2 From 860cba13432e071442a39e9bab83258c631d33a6 Mon Sep 17 00:00:00 2001 From: Nicholas Mc Guire Date: Mon, 9 Feb 2015 14:24:25 -0500 Subject: misc: carma: fixup return type of wait_for_completion_timeout return type of wait_for_completion_timeout is unsigned long not int. The rc variable is renamed timeout to reflect its use and the type adjusted to unsigned long. Signed-off-by: Nicholas Mc Guire Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/misc/carma/carma-fpga-program.c b/drivers/misc/carma/carma-fpga-program.c index fff8c43..0b1bd85 100644 --- a/drivers/misc/carma/carma-fpga-program.c +++ b/drivers/misc/carma/carma-fpga-program.c @@ -479,6 +479,7 @@ static int fpga_program_block(struct fpga_dev *priv, void *buf, size_t count) static noinline int fpga_program_cpu(struct fpga_dev *priv) { int ret; + unsigned long timeout; /* Disable the programmer */ fpga_programmer_disable(priv); @@ -497,8 +498,8 @@ static noinline int fpga_program_cpu(struct fpga_dev *priv) goto out_disable_controller; /* Wait for the interrupt handler to signal that programming finished */ - ret = wait_for_completion_timeout(&priv->completion, 2 * HZ); - if (!ret) { + timeout = wait_for_completion_timeout(&priv->completion, 2 * HZ); + if (!timeout) { dev_err(priv->dev, "Timed out waiting for completion\n"); ret = -ETIMEDOUT; goto out_disable_controller; @@ -536,6 +537,7 @@ static noinline int fpga_program_dma(struct fpga_dev *priv) struct sg_table table; dma_cookie_t cookie; int ret, i; + unsigned long timeout; /* Disable the programmer */ fpga_programmer_disable(priv); @@ -623,8 +625,8 @@ static noinline int fpga_program_dma(struct fpga_dev *priv) dev_dbg(priv->dev, "enabled the controller\n"); /* Wait for the interrupt handler to signal that programming finished */ - ret = wait_for_completion_timeout(&priv->completion, 2 * HZ); - if (!ret) { + timeout = wait_for_completion_timeout(&priv->completion, 2 * HZ); + if (!timeout) { dev_err(priv->dev, "Timed out waiting for completion\n"); ret = -ETIMEDOUT; goto out_disable_controller; -- cgit v0.10.2 From 2f9763190dd6356eec95548f4cfa8a36a71f767c Mon Sep 17 00:00:00 2001 From: Nicholas Mc Guire Date: Mon, 9 Feb 2015 14:09:09 -0500 Subject: misc: mic: fixup return type of wait_for_completion_timeout return type of wait_for_completion_timeout is unsigned long not int. The rc variable is renamed timeout to reflect its use and the type adjusted to unsigned long. Signed-off-by: Nicholas Mc Guire Acked-by: Sudeep Dutt Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/misc/mic/host/mic_boot.c b/drivers/misc/mic/host/mic_boot.c index ff2b0fb..d9fa609 100644 --- a/drivers/misc/mic/host/mic_boot.c +++ b/drivers/misc/mic/host/mic_boot.c @@ -309,7 +309,7 @@ void mic_complete_resume(struct mic_device *mdev) */ void mic_prepare_suspend(struct mic_device *mdev) { - int rc; + unsigned long timeout; #define MIC_SUSPEND_TIMEOUT (60 * HZ) @@ -331,10 +331,10 @@ void mic_prepare_suspend(struct mic_device *mdev) */ mic_set_state(mdev, MIC_SUSPENDING); mutex_unlock(&mdev->mic_mutex); - rc = wait_for_completion_timeout(&mdev->reset_wait, - MIC_SUSPEND_TIMEOUT); + timeout = wait_for_completion_timeout(&mdev->reset_wait, + MIC_SUSPEND_TIMEOUT); /* Force reset the card if the shutdown completion timed out */ - if (!rc) { + if (!timeout) { mutex_lock(&mdev->mic_mutex); mic_set_state(mdev, MIC_SUSPENDED); mutex_unlock(&mdev->mic_mutex); @@ -348,10 +348,10 @@ void mic_prepare_suspend(struct mic_device *mdev) */ mic_set_state(mdev, MIC_SUSPENDED); mutex_unlock(&mdev->mic_mutex); - rc = wait_for_completion_timeout(&mdev->reset_wait, - MIC_SUSPEND_TIMEOUT); + timeout = wait_for_completion_timeout(&mdev->reset_wait, + MIC_SUSPEND_TIMEOUT); /* Force reset the card if the shutdown completion timed out */ - if (!rc) + if (!timeout) mic_stop(mdev, true); break; default: -- cgit v0.10.2 From bd735995308b553cc3c7f6a975aa284b270c7e2c Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 2 Feb 2015 15:44:54 +0100 Subject: misc: Add attribute groups Add groups field to struct miscdevice for passing the attribute groups at device creation. In this way, the driver can avoid the manual call of device_create_file() after the device registration, which is basically a racy operation, in addition to the reduction of manual device_remove_file() calls. Signed-off-by: Takashi Iwai Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/char/misc.c b/drivers/char/misc.c index c892c29..5bb3a21 100644 --- a/drivers/char/misc.c +++ b/drivers/char/misc.c @@ -207,8 +207,9 @@ int misc_register(struct miscdevice * misc) dev = MKDEV(MISC_MAJOR, misc->minor); - misc->this_device = device_create(misc_class, misc->parent, dev, - misc, "%s", misc->name); + misc->this_device = + device_create_with_groups(misc_class, misc->parent, dev, + misc, misc->groups, "%s", misc->name); if (IS_ERR(misc->this_device)) { int i = DYNAMIC_MINORS - misc->minor - 1; if (i < DYNAMIC_MINORS && i >= 0) diff --git a/include/linux/miscdevice.h b/include/linux/miscdevice.h index ee80dd7..819077c 100644 --- a/include/linux/miscdevice.h +++ b/include/linux/miscdevice.h @@ -52,6 +52,7 @@ #define MISC_DYNAMIC_MINOR 255 struct device; +struct attribute_group; struct miscdevice { int minor; @@ -60,6 +61,7 @@ struct miscdevice { struct list_head list; struct device *parent; struct device *this_device; + const struct attribute_group **groups; const char *nodename; umode_t mode; }; -- cgit v0.10.2 From 0daa7a0afd0fa7aad83cbde0fb341d7fbeac952c Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 2 Feb 2015 15:44:55 +0100 Subject: hwrng: Avoid manual device_create_file() calls Use the new group field of struct miscdevice for managing the sysfs entries instead of manually adding/removing via device_create_file() and device_remove_file(). This simplifies the code a lot and fixes the possible races. Signed-off-by: Takashi Iwai Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/char/hw_random/core.c b/drivers/char/hw_random/core.c index 32a8a86..0a81945 100644 --- a/drivers/char/hw_random/core.c +++ b/drivers/char/hw_random/core.c @@ -299,11 +299,14 @@ static const struct file_operations rng_chrdev_ops = { .llseek = noop_llseek, }; +static const struct attribute_group *rng_dev_groups[]; + static struct miscdevice rng_miscdev = { .minor = RNG_MISCDEV_MINOR, .name = RNG_MODULE_NAME, .nodename = "hwrng", .fops = &rng_chrdev_ops, + .groups = rng_dev_groups, }; @@ -376,37 +379,22 @@ static DEVICE_ATTR(rng_available, S_IRUGO, hwrng_attr_available_show, NULL); +static struct attribute *rng_dev_attrs[] = { + &dev_attr_rng_current.attr, + &dev_attr_rng_available.attr, + NULL +}; + +ATTRIBUTE_GROUPS(rng_dev); static void __exit unregister_miscdev(void) { - device_remove_file(rng_miscdev.this_device, &dev_attr_rng_available); - device_remove_file(rng_miscdev.this_device, &dev_attr_rng_current); misc_deregister(&rng_miscdev); } static int __init register_miscdev(void) { - int err; - - err = misc_register(&rng_miscdev); - if (err) - goto out; - err = device_create_file(rng_miscdev.this_device, - &dev_attr_rng_current); - if (err) - goto err_misc_dereg; - err = device_create_file(rng_miscdev.this_device, - &dev_attr_rng_available); - if (err) - goto err_remove_current; -out: - return err; - -err_remove_current: - device_remove_file(rng_miscdev.this_device, &dev_attr_rng_current); -err_misc_dereg: - misc_deregister(&rng_miscdev); - goto out; + return misc_register(&rng_miscdev); } static int hwrng_fillfn(void *unused) -- cgit v0.10.2 From fde25d25dbd997067058f7d4c2ff31600157e6f2 Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Wed, 18 Mar 2015 12:29:21 -0700 Subject: Drivers: hv: vmbus: Perform device register in the per-channel work element This patch is a continuation of the rescind handling cleanup work. We cannot block in the global message handling work context especially if we are blocking waiting for the host to wake us up. I would like to thank Dexuan Cui for observing this problem. The current char-next branch is broken and this patch fixes the bug. Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/hv/channel_mgmt.c b/drivers/hv/channel_mgmt.c index 6117891..5f8e47b 100644 --- a/drivers/hv/channel_mgmt.c +++ b/drivers/hv/channel_mgmt.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -37,6 +38,10 @@ struct vmbus_channel_message_table_entry { void (*message_handler)(struct vmbus_channel_message_header *msg); }; +struct vmbus_rescind_work { + struct work_struct work; + struct vmbus_channel *channel; +}; /** * vmbus_prep_negotiate_resp() - Create default response for Hyper-V Negotiate message @@ -134,20 +139,6 @@ fw_error: EXPORT_SYMBOL_GPL(vmbus_prep_negotiate_resp); -static void vmbus_process_device_unregister(struct work_struct *work) -{ - struct device *dev; - struct vmbus_channel *channel = container_of(work, - struct vmbus_channel, - work); - - dev = get_device(&channel->device_obj->device); - if (dev) { - vmbus_device_unregister(channel->device_obj); - put_device(dev); - } -} - static void vmbus_sc_creation_cb(struct work_struct *work) { struct vmbus_channel *newchannel = container_of(work, @@ -220,6 +211,40 @@ static void free_channel(struct vmbus_channel *channel) queue_work(vmbus_connection.work_queue, &channel->work); } +static void process_rescind_fn(struct work_struct *work) +{ + struct vmbus_rescind_work *rc_work; + struct vmbus_channel *channel; + struct device *dev; + + rc_work = container_of(work, struct vmbus_rescind_work, work); + channel = rc_work->channel; + + /* + * We have already acquired a reference on the channel + * and so it cannot vanish underneath us. + * It is possible (while very unlikely) that we may + * get here while the processing of the initial offer + * is still not complete. Deal with this situation by + * just waiting until the channel is in the correct state. + */ + + while (channel->work.func != release_channel) + msleep(1000); + + if (channel->device_obj) { + dev = get_device(&channel->device_obj->device); + if (dev) { + vmbus_device_unregister(channel->device_obj); + put_device(dev); + } + } else { + hv_process_channel_removal(channel, + channel->offermsg.child_relid); + } + kfree(work); +} + static void percpu_channel_enq(void *arg) { struct vmbus_channel *channel = arg; @@ -282,6 +307,46 @@ void vmbus_free_channels(void) } } +static void vmbus_do_device_register(struct work_struct *work) +{ + struct hv_device *device_obj; + int ret; + unsigned long flags; + struct vmbus_channel *newchannel = container_of(work, + struct vmbus_channel, + work); + + ret = vmbus_device_register(newchannel->device_obj); + if (ret != 0) { + pr_err("unable to add child device object (relid %d)\n", + newchannel->offermsg.child_relid); + spin_lock_irqsave(&vmbus_connection.channel_lock, flags); + list_del(&newchannel->listentry); + device_obj = newchannel->device_obj; + newchannel->device_obj = NULL; + spin_unlock_irqrestore(&vmbus_connection.channel_lock, flags); + + if (newchannel->target_cpu != get_cpu()) { + put_cpu(); + smp_call_function_single(newchannel->target_cpu, + percpu_channel_deq, newchannel, true); + } else { + percpu_channel_deq(newchannel); + put_cpu(); + } + + kfree(device_obj); + if (!newchannel->rescind) { + free_channel(newchannel); + return; + } + } + /* + * The next state for this channel is to be freed. + */ + INIT_WORK(&newchannel->work, release_channel); +} + /* * vmbus_process_offer - Process the offer by creating a channel/device * associated with this offer @@ -291,7 +356,6 @@ static void vmbus_process_offer(struct vmbus_channel *newchannel) struct vmbus_channel *channel; bool fnew = true; bool enq = false; - int ret; unsigned long flags; /* Make sure this is a new offer */ @@ -393,16 +457,13 @@ static void vmbus_process_offer(struct vmbus_channel *newchannel) * Add the new device to the bus. This will kick off device-driver * binding which eventually invokes the device driver's AddDevice() * method. + * Invoke this call on the per-channel work context. + * Until we return from this function, rescind offer message + * cannot be processed as we are running on the global message + * handling work. */ - ret = vmbus_device_register(newchannel->device_obj); - if (ret != 0) { - pr_err("unable to add child device object (relid %d)\n", - newchannel->offermsg.child_relid); - - kfree(newchannel->device_obj); - goto err_deq_chan; - } - + INIT_WORK(&newchannel->work, vmbus_do_device_register); + queue_work(newchannel->controlwq, &newchannel->work); return; err_deq_chan: @@ -556,33 +617,31 @@ static void vmbus_onoffer_rescind(struct vmbus_channel_message_header *hdr) { struct vmbus_channel_rescind_offer *rescind; struct vmbus_channel *channel; - unsigned long flags; + struct vmbus_rescind_work *rc_work; rescind = (struct vmbus_channel_rescind_offer *)hdr; - channel = relid2channel(rescind->child_relid); + channel = relid2channel(rescind->child_relid, true); if (channel == NULL) { hv_process_channel_removal(NULL, rescind->child_relid); return; } - spin_lock_irqsave(&channel->lock, flags); - channel->rescind = true; - spin_unlock_irqrestore(&channel->lock, flags); - - if (channel->device_obj) { - /* - * We will have to unregister this device from the - * driver core. Do this in the per-channel work context. - * Note that we are currently executing on the global - * workq for handling messages from the host. - */ - INIT_WORK(&channel->work, vmbus_process_device_unregister); - queue_work(channel->controlwq, &channel->work); - } else { - hv_process_channel_removal(channel, - channel->offermsg.child_relid); + /* + * We have acquired a reference on the channel and have posted + * the rescind state. Perform further cleanup in a work context + * that is different from the global work context in which + * we process messages from the host (we are currently executing + * on that global context. + */ + rc_work = kzalloc(sizeof(struct vmbus_rescind_work), GFP_KERNEL); + if (!rc_work) { + pr_err("Unable to allocate memory for rescind processing "); + return; } + rc_work->channel = channel; + INIT_WORK(&rc_work->work, process_rescind_fn); + schedule_work(&rc_work->work); } /* diff --git a/drivers/hv/connection.c b/drivers/hv/connection.c index 583d7d4..8bcd307 100644 --- a/drivers/hv/connection.c +++ b/drivers/hv/connection.c @@ -270,7 +270,7 @@ static struct vmbus_channel *pcpu_relid2channel(u32 relid) * relid2channel - Get the channel object given its * child relative id (ie channel id) */ -struct vmbus_channel *relid2channel(u32 relid) +struct vmbus_channel *relid2channel(u32 relid, bool rescind) { struct vmbus_channel *channel; struct vmbus_channel *found_channel = NULL; @@ -282,6 +282,8 @@ struct vmbus_channel *relid2channel(u32 relid) list_for_each_entry(channel, &vmbus_connection.chn_list, listentry) { if (channel->offermsg.child_relid == relid) { found_channel = channel; + if (rescind) + found_channel->rescind = true; break; } else if (!list_empty(&channel->sc_list)) { /* @@ -292,6 +294,8 @@ struct vmbus_channel *relid2channel(u32 relid) sc_list); if (cur_sc->offermsg.child_relid == relid) { found_channel = cur_sc; + if (rescind) + found_channel->rescind = true; break; } } diff --git a/drivers/hv/hyperv_vmbus.h b/drivers/hv/hyperv_vmbus.h index 88af4ec..6339589 100644 --- a/drivers/hv/hyperv_vmbus.h +++ b/drivers/hv/hyperv_vmbus.h @@ -698,7 +698,7 @@ void vmbus_device_unregister(struct hv_device *device_obj); /* VmbusChildDeviceDestroy( */ /* struct hv_device *); */ -struct vmbus_channel *relid2channel(u32 relid); +struct vmbus_channel *relid2channel(u32 relid, bool rescind); void vmbus_free_channels(void); -- cgit v0.10.2 From f3f6eb80872becdbce07f7d2670d3f8ae5119752 Mon Sep 17 00:00:00 2001 From: Vitaly Kuznetsov Date: Wed, 18 Mar 2015 12:29:22 -0700 Subject: Drivers: hv: hv_balloon: keep locks balanced on add_memory() failure When add_memory() fails the following BUG is observed: [ 743.646107] hv_balloon: hot_add memory failed error is -17 [ 743.679973] [ 743.680930] ===================================== [ 743.680930] [ BUG: bad unlock balance detected! ] [ 743.680930] 3.19.0-rc5_bug1131426+ #552 Not tainted [ 743.680930] ------------------------------------- [ 743.680930] kworker/0:2/255 is trying to release lock (&dm_device.ha_region_mutex) at: [ 743.680930] [] mutex_unlock+0xe/0x10 [ 743.680930] but there are no more locks to release! This happens as we don't acquire ha_region_mutex and hot_add_req() expects us to as it does unconditional mutex_unlock(). Acquire the lock on the error path. The current char-next branch is broken and this patch fixes the bug. Signed-off-by: Vitaly Kuznetsov Acked-by: Jason Wang Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/hv/hv_balloon.c b/drivers/hv/hv_balloon.c index c5bb872..f1f17c5 100644 --- a/drivers/hv/hv_balloon.c +++ b/drivers/hv/hv_balloon.c @@ -652,6 +652,7 @@ static void hv_mem_hot_add(unsigned long start, unsigned long size, } has->ha_end_pfn -= HA_CHUNK; has->covered_end_pfn -= processed_pfn; + mutex_lock(&dm_device.ha_region_mutex); break; } -- cgit v0.10.2 From 5abbbb75d733793b1637441691de95b7665cee85 Mon Sep 17 00:00:00 2001 From: Vitaly Kuznetsov Date: Wed, 18 Mar 2015 12:29:23 -0700 Subject: Drivers: hv: hv_balloon: don't lose memory when onlining order is not natural Memory blocks can be onlined in random order. When this order is not natural some memory pages are not onlined because of the redundant check in hv_online_page(). Here is a real world scenario: 1) Host tries to hot-add the following (process_hot_add): pg_start=rg_start=0x48000, pfn_cnt=111616, rg_size=262144 2) This results in adding 4 memory blocks: [ 109.057866] init_memory_mapping: [mem 0x48000000-0x4fffffff] [ 114.102698] init_memory_mapping: [mem 0x50000000-0x57ffffff] [ 119.168039] init_memory_mapping: [mem 0x58000000-0x5fffffff] [ 124.233053] init_memory_mapping: [mem 0x60000000-0x67ffffff] The last one is incomplete but we have special has->covered_end_pfn counter to avoid onlining non-backed frames and hv_bring_pgs_online() function to bring them online later on. 3) Now we have 4 offline memory blocks: /sys/devices/system/memory/memory9-12 $ for f in /sys/devices/system/memory/memory*/state; do echo $f `cat $f`; done | grep -v onlin /sys/devices/system/memory/memory10/state offline /sys/devices/system/memory/memory11/state offline /sys/devices/system/memory/memory12/state offline /sys/devices/system/memory/memory9/state offline 4) We bring them online in non-natural order: $grep MemTotal /proc/meminfo MemTotal: 966348 kB $echo online > /sys/devices/system/memory/memory12/state && grep MemTotal /proc/meminfo MemTotal: 1019596 kB $echo online > /sys/devices/system/memory/memory11/state && grep MemTotal /proc/meminfo MemTotal: 1150668 kB $echo online > /sys/devices/system/memory/memory9/state && grep MemTotal /proc/meminfo MemTotal: 1150668 kB As you can see memory9 block gives us zero additional memory. We can also observe a huge discrepancy between host- and guest-reported memory sizes. The root cause of the issue is the redundant pg >= covered_start_pfn check (and covered_start_pfn advancing) in hv_online_page(). When upper memory block in being onlined before the lower one (memory12 and memory11 in the above case) we advance the covered_start_pfn pointer and all memory9 pages do not pass the check. If the assumption that host always gives us requests in sequential order and pg_start always equals rg_start when the first request for the new HA region is received (that's the case in my testing) is correct than we can get rid of covered_start_pfn and pg >= start_pfn check in hv_online_page() is sufficient. The current char-next branch is broken and this patch fixes the bug. Signed-off-by: Vitaly Kuznetsov Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/hv/hv_balloon.c b/drivers/hv/hv_balloon.c index f1f17c5..014256a 100644 --- a/drivers/hv/hv_balloon.c +++ b/drivers/hv/hv_balloon.c @@ -428,14 +428,13 @@ struct dm_info_msg { * currently hot added. We hot add in multiples of 128M * chunks; it is possible that we may not be able to bring * online all the pages in the region. The range - * covered_start_pfn : covered_end_pfn defines the pages that can + * covered_end_pfn defines the pages that can * be brough online. */ struct hv_hotadd_state { struct list_head list; unsigned long start_pfn; - unsigned long covered_start_pfn; unsigned long covered_end_pfn; unsigned long ha_end_pfn; unsigned long end_pfn; @@ -679,8 +678,7 @@ static void hv_online_page(struct page *pg) list_for_each(cur, &dm_device.ha_region_list) { has = list_entry(cur, struct hv_hotadd_state, list); - cur_start_pgp = (unsigned long) - pfn_to_page(has->covered_start_pfn); + cur_start_pgp = (unsigned long)pfn_to_page(has->start_pfn); cur_end_pgp = (unsigned long)pfn_to_page(has->covered_end_pfn); if (((unsigned long)pg >= cur_start_pgp) && @@ -692,7 +690,6 @@ static void hv_online_page(struct page *pg) __online_page_set_limits(pg); __online_page_increment_counters(pg); __online_page_free(pg); - has->covered_start_pfn++; } } } @@ -736,10 +733,9 @@ static bool pfn_covered(unsigned long start_pfn, unsigned long pfn_cnt) * is, update it. */ - if (has->covered_end_pfn != start_pfn) { + if (has->covered_end_pfn != start_pfn) has->covered_end_pfn = start_pfn; - has->covered_start_pfn = start_pfn; - } + return true; } @@ -784,7 +780,6 @@ static unsigned long handle_pg_range(unsigned long pg_start, pgs_ol = pfn_cnt; hv_bring_pgs_online(start_pfn, pgs_ol); has->covered_end_pfn += pgs_ol; - has->covered_start_pfn += pgs_ol; pfn_cnt -= pgs_ol; } @@ -845,7 +840,6 @@ static unsigned long process_hot_add(unsigned long pg_start, list_add_tail(&ha_region->list, &dm_device.ha_region_list); ha_region->start_pfn = rg_start; ha_region->ha_end_pfn = rg_start; - ha_region->covered_start_pfn = pg_start; ha_region->covered_end_pfn = pg_start; ha_region->end_pfn = rg_start + rg_size; } -- cgit v0.10.2 From 5ef5b6927f14f29cacd78fa1fb861661a5367f13 Mon Sep 17 00:00:00 2001 From: Nick Meier Date: Wed, 18 Mar 2015 12:29:24 -0700 Subject: Drivers: hv: vmbus: Correcting truncation error for constant HV_CRASH_CTL_CRASH_NOTIFY HV_CRASH_CTL_CRASH_NOTIFY is a 64 bit number. Depending on the usage context, the value may be truncated. This patch is in response from the following email from Wu Fengguang : From: Wu Fengguang Subject: [char-misc:char-misc-testing 25/45] drivers/hv/vmbus_drv.c:67:9: sparse: constant 0x8000000000000000 is so big it is unsigned long tree: git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc.git char-misc-testing head: b3de8e3719e582f3182bb504295e4a8e43c8c96f commit: 96c1d0581d00f7abe033350edb021a9d947d8d81 [25/45] Drivers: hv: vmbus: Add support for VMBus panic notifier handler reproduce: # apt-get install sparse git checkout 96c1d0581d00f7abe033350edb021a9d947d8d81 make ARCH=x86_64 allmodconfig make C=1 CF=-D__CHECK_ENDIAN__ sparse warnings: (new ones prefixed by >>) drivers/hv/vmbus_drv.c:67:9: sparse: constant 0x8000000000000000 is so big it is unsigned long ... Signed-off-by: Nick Meier Reported-by: Wu Fengguang Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/hv/hyperv_vmbus.h b/drivers/hv/hyperv_vmbus.h index 6339589..c8e27e0 100644 --- a/drivers/hv/hyperv_vmbus.h +++ b/drivers/hv/hyperv_vmbus.h @@ -58,7 +58,7 @@ enum hv_cpuid_function { #define HV_X64_MSR_CRASH_P4 0x40000104 #define HV_X64_MSR_CRASH_CTL 0x40000105 -#define HV_CRASH_CTL_CRASH_NOTIFY 0x8000000000000000 +#define HV_CRASH_CTL_CRASH_NOTIFY (1ULL << 63) /* Define version of the synthetic interrupt controller. */ #define HV_SYNIC_VERSION (1) -- cgit v0.10.2 From 177757423fde68a6ce773d2b67b468fbd8367bbc Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Wed, 18 Mar 2015 12:29:25 -0700 Subject: hv: vmbus: missing curly braces in vmbus_process_offer() The indenting makes it clear that there were curly braces intended here. Fixes: 2dd37cb81580 ('Drivers: hv: vmbus: Handle both rescind and offer messages in the same context') Signed-off-by: Dan Carpenter Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/hv/channel_mgmt.c b/drivers/hv/channel_mgmt.c index 5f8e47b..25dbbaf 100644 --- a/drivers/hv/channel_mgmt.c +++ b/drivers/hv/channel_mgmt.c @@ -415,7 +415,7 @@ static void vmbus_process_offer(struct vmbus_channel *newchannel) newchannel->state = CHANNEL_OPEN_STATE; channel->num_sc++; - if (channel->sc_creation_callback != NULL) + if (channel->sc_creation_callback != NULL) { /* * We need to invoke the sub-channel creation * callback; invoke this in a seperate work @@ -427,6 +427,7 @@ static void vmbus_process_offer(struct vmbus_channel *newchannel) vmbus_sc_creation_cb); queue_work(newchannel->controlwq, &newchannel->work); + } return; } -- cgit v0.10.2 From b4affbbb7217e440f80c922b2b304795758d40bb Mon Sep 17 00:00:00 2001 From: Dexuan Cui Date: Wed, 18 Mar 2015 12:29:26 -0700 Subject: tools: hv: fcopy_daemon: support >2GB files for x86_32 guest Without this patch, hv_fcopy_daemon's hv_copy_data() -> pwrite() will fail for >2GB file offset. The current char-next branch is broken and this patch fixes the bug. Signed-off-by: Alex Ng Signed-off-by: Dexuan Cui Cc: K. Y. Srinivasan Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman diff --git a/tools/hv/Makefile b/tools/hv/Makefile index 99ffe61..a8ab795 100644 --- a/tools/hv/Makefile +++ b/tools/hv/Makefile @@ -3,7 +3,7 @@ CC = $(CROSS_COMPILE)gcc PTHREAD_LIBS = -lpthread WARNINGS = -Wall -Wextra -CFLAGS = $(WARNINGS) -g $(PTHREAD_LIBS) +CFLAGS = $(WARNINGS) -g $(PTHREAD_LIBS) $(shell getconf LFS_CFLAGS) all: hv_kvp_daemon hv_vss_daemon hv_fcopy_daemon %: %.c -- cgit v0.10.2 From f2eddbc9f1a466329c68f3b75e89cfacd2792365 Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Wed, 18 Mar 2015 12:29:27 -0700 Subject: Drivers: hv: vmbus: Fix a bug in rescind processing in vmbus_close_internal() When a channel has been rescinded, the close operation is a noop. Restructure the code so we deal with the rescind condition after we properly cleanup the channel. I would like to thank Dexuan Cui for observing this problem. The current code leaks memory when the channel is rescinded. The current char-next branch is broken and this patch fixes the bug. Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/hv/channel.c b/drivers/hv/channel.c index da53180..2c8206d 100644 --- a/drivers/hv/channel.c +++ b/drivers/hv/channel.c @@ -501,15 +501,6 @@ static int vmbus_close_internal(struct vmbus_channel *channel) put_cpu(); } - /* - * If the channel has been rescinded; process device removal. - */ - if (channel->rescind) { - hv_process_channel_removal(channel, - channel->offermsg.child_relid); - return 0; - } - /* Send a closing message */ msg = &channel->close_msg.msg; @@ -549,6 +540,12 @@ static int vmbus_close_internal(struct vmbus_channel *channel) free_pages((unsigned long)channel->ringbuffer_pages, get_order(channel->ringbuffer_pagecount * PAGE_SIZE)); + /* + * If the channel has been rescinded; process device removal. + */ + if (channel->rescind) + hv_process_channel_removal(channel, + channel->offermsg.child_relid); return ret; } -- cgit v0.10.2 From 4ce50e9491c5c92baf8bb7af85eb25398d3f70af Mon Sep 17 00:00:00 2001 From: Vaughan Cao Date: Wed, 18 Mar 2015 12:29:28 -0700 Subject: hv: hypervvssd: call endmntent before call setmntent again If freeze fails, vss_operate will re-enter itself to thaw. But it forgets to call endmntent() before it recalls setmntent() again. Signed-off-by: Vaughan Cao Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman diff --git a/tools/hv/hv_vss_daemon.c b/tools/hv/hv_vss_daemon.c index 5e63f70..506dd01 100644 --- a/tools/hv/hv_vss_daemon.c +++ b/tools/hv/hv_vss_daemon.c @@ -81,6 +81,7 @@ static int vss_operate(int operation) char match[] = "/dev/"; FILE *mounts; struct mntent *ent; + char errdir[1024] = {0}; unsigned int cmd; int error = 0, root_seen = 0, save_errno = 0; @@ -115,6 +116,8 @@ static int vss_operate(int operation) goto err; } + endmntent(mounts); + if (root_seen) { error |= vss_do_freeze("/", cmd); if (error && operation == VSS_OP_FREEZE) @@ -124,16 +127,19 @@ static int vss_operate(int operation) goto out; err: save_errno = errno; + if (ent) { + strncpy(errdir, ent->mnt_dir, sizeof(errdir)-1); + endmntent(mounts); + } vss_operate(VSS_OP_THAW); /* Call syslog after we thaw all filesystems */ if (ent) syslog(LOG_ERR, "FREEZE of %s failed; error:%d %s", - ent->mnt_dir, save_errno, strerror(save_errno)); + errdir, save_errno, strerror(save_errno)); else syslog(LOG_ERR, "FREEZE of / failed; error:%d %s", save_errno, strerror(save_errno)); out: - endmntent(mounts); return error; } -- cgit v0.10.2 From b3a19b36ad7450d4c8caeb7d9f623af3da32a578 Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Wed, 18 Mar 2015 12:29:29 -0700 Subject: Drivers: hv: vmbus: Export the vmbus_sendpacket_pagebuffer_ctl() Export the vmbus_sendpacket_pagebuffer_ctl() interface. This export will be used by the netvsc driver. Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/hv/channel.c b/drivers/hv/channel.c index 2c8206d..fddd3b5 100644 --- a/drivers/hv/channel.c +++ b/drivers/hv/channel.c @@ -707,6 +707,7 @@ int vmbus_sendpacket_pagebuffer_ctl(struct vmbus_channel *channel, return ret; } +EXPORT_SYMBOL_GPL(vmbus_sendpacket_pagebuffer_ctl); /* * vmbus_sendpacket_pagebuffer - Send a range of single-page buffer -- cgit v0.10.2 From 5f5cc81733baf7b67a039a931dae282e53bf97b9 Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Wed, 18 Mar 2015 12:29:30 -0700 Subject: Drivers: hv: vmbus: Fix a siganlling host signalling issue Handle the case when the write to the ringbuffer fails. In this case, unconditionally signal the host. Since we may have deferred signalling the host based on the kick_q parameter, signalling the host unconditionally in this case deals with the issue. Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/hv/channel.c b/drivers/hv/channel.c index fddd3b5..54da66d 100644 --- a/drivers/hv/channel.c +++ b/drivers/hv/channel.c @@ -611,7 +611,19 @@ int vmbus_sendpacket_ctl(struct vmbus_channel *channel, void *buffer, ret = hv_ringbuffer_write(&channel->outbound, bufferlist, 3, &signal); - if ((ret == 0) && kick_q && signal) + /* + * Signalling the host is conditional on many factors: + * 1. The ring state changed from being empty to non-empty. + * This is tracked by the variable "signal". + * 2. The variable kick_q tracks if more data will be placed + * on the ring. We will not signal if more data is + * to be placed. + * + * 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. + */ + if (((ret == 0) && kick_q && signal) || (ret)) vmbus_setevent(channel); return ret; @@ -702,7 +714,19 @@ int vmbus_sendpacket_pagebuffer_ctl(struct vmbus_channel *channel, ret = hv_ringbuffer_write(&channel->outbound, bufferlist, 3, &signal); - if ((ret == 0) && kick_q && signal) + /* + * Signalling the host is conditional on many factors: + * 1. The ring state changed from being empty to non-empty. + * This is tracked by the variable "signal". + * 2. The variable kick_q tracks if more data will be placed + * on the ring. We will not signal if more data is + * to be placed. + * + * 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. + */ + if (((ret == 0) && kick_q && signal) || (ret)) vmbus_setevent(channel); return ret; -- cgit v0.10.2 From 4d8beff2ae07fad85d723b4cdf704b05f0ed4794 Mon Sep 17 00:00:00 2001 From: Fabian Frederick Date: Mon, 16 Mar 2015 20:54:38 +0100 Subject: uio: constify of_device_id array of_device_id is always used as const. (See driver.of_match_table and open firmware functions) Signed-off-by: Fabian Frederick Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/uio/uio_pdrv_genirq.c b/drivers/uio/uio_pdrv_genirq.c index f598ecd..59c172a 100644 --- a/drivers/uio/uio_pdrv_genirq.c +++ b/drivers/uio/uio_pdrv_genirq.c @@ -252,7 +252,7 @@ static const struct dev_pm_ops uio_pdrv_genirq_dev_pm_ops = { }; #ifdef CONFIG_OF -static struct of_device_id uio_of_genirq_match[] = { +static const struct of_device_id uio_of_genirq_match[] = { { /* This is filled with module_parm */ }, { /* Sentinel */ }, }; -- cgit v0.10.2 From 0a56c0e1e7ad4dc3721e96c499f3074cb6867cfe Mon Sep 17 00:00:00 2001 From: Fabian Frederick Date: Mon, 16 Mar 2015 20:20:29 +0100 Subject: w1: constify of_device_id array of_device_id is always used as const. (See driver.of_match_table and open firmware functions) Signed-off-by: Fabian Frederick Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/w1/masters/mxc_w1.c b/drivers/w1/masters/mxc_w1.c index 53bf2c8..a462175 100644 --- a/drivers/w1/masters/mxc_w1.c +++ b/drivers/w1/masters/mxc_w1.c @@ -166,7 +166,7 @@ static int mxc_w1_remove(struct platform_device *pdev) return 0; } -static struct of_device_id mxc_w1_dt_ids[] = { +static const struct of_device_id mxc_w1_dt_ids[] = { { .compatible = "fsl,imx21-owire" }, { /* sentinel */ } }; diff --git a/drivers/w1/masters/omap_hdq.c b/drivers/w1/masters/omap_hdq.c index 03321d6..e7d4489 100644 --- a/drivers/w1/masters/omap_hdq.c +++ b/drivers/w1/masters/omap_hdq.c @@ -72,7 +72,7 @@ struct hdq_data { static int omap_hdq_probe(struct platform_device *pdev); static int omap_hdq_remove(struct platform_device *pdev); -static struct of_device_id omap_hdq_dt_ids[] = { +static const struct of_device_id omap_hdq_dt_ids[] = { { .compatible = "ti,omap3-1w" }, {} }; diff --git a/drivers/w1/masters/w1-gpio.c b/drivers/w1/masters/w1-gpio.c index b99a932..8f7848c 100644 --- a/drivers/w1/masters/w1-gpio.c +++ b/drivers/w1/masters/w1-gpio.c @@ -68,7 +68,7 @@ static u8 w1_gpio_read_bit(void *data) } #if defined(CONFIG_OF) -static struct of_device_id w1_gpio_dt_ids[] = { +static const struct of_device_id w1_gpio_dt_ids[] = { { .compatible = "w1-gpio" }, {} }; -- cgit v0.10.2 From 3a267d3b22f56bf5ba379c20604fd28409119698 Mon Sep 17 00:00:00 2001 From: Joe Perches Date: Sat, 21 Feb 2015 18:53:47 -0800 Subject: i8k: Remove use of seq_printf return value The seq_printf return value, because it's frequently misused, will eventually be converted to void. See: commit 1f33c41c03da ("seq_file: Rename seq_overflow() to seq_has_overflowed() and make public") Signed-off-by: Joe Perches Acked-by: Guenter Roeck Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/char/i8k.c b/drivers/char/i8k.c index 24cc4ed..a43048b 100644 --- a/drivers/char/i8k.c +++ b/drivers/char/i8k.c @@ -510,13 +510,15 @@ static int i8k_proc_show(struct seq_file *seq, void *offset) * 9) AC power * 10) Fn Key status */ - return seq_printf(seq, "%s %s %s %d %d %d %d %d %d %d\n", - I8K_PROC_FMT, - bios_version, - i8k_get_dmi_data(DMI_PRODUCT_SERIAL), - cpu_temp, - left_fan, right_fan, left_speed, right_speed, - ac_power, fn_key); + seq_printf(seq, "%s %s %s %d %d %d %d %d %d %d\n", + I8K_PROC_FMT, + bios_version, + i8k_get_dmi_data(DMI_PRODUCT_SERIAL), + cpu_temp, + left_fan, right_fan, left_speed, right_speed, + ac_power, fn_key); + + return 0; } static int i8k_open_fs(struct inode *inode, struct file *file) -- cgit v0.10.2 From 160b7daab96636d089fee4e0487da49c70ca4e15 Mon Sep 17 00:00:00 2001 From: Fabian Frederick Date: Mon, 16 Mar 2015 20:20:33 +0100 Subject: coresight-replicator: constify of_device_id array of_device_id is always used as const. (See driver.of_match_table and open firmware functions) Signed-off-by: Fabian Frederick Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/coresight/coresight-replicator.c b/drivers/coresight/coresight-replicator.c index cdf0553..75b9abd 100644 --- a/drivers/coresight/coresight-replicator.c +++ b/drivers/coresight/coresight-replicator.c @@ -107,7 +107,7 @@ static int replicator_remove(struct platform_device *pdev) return 0; } -static struct of_device_id replicator_match[] = { +static const struct of_device_id replicator_match[] = { {.compatible = "arm,coresight-replicator"}, {} }; -- cgit v0.10.2 From 73cffdb65e679b98893f484063462c045adcf212 Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Thu, 19 Mar 2015 08:11:34 -0700 Subject: Drivers: hv: vmbus: Don't wait after requesting offers Don't wait after sending request for offers to the host. This wait is unnecessary and simply adds 5 seconds to the boot time. Signed-off-by: K. Y. Srinivasan Cc: Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/hv/channel_mgmt.c b/drivers/hv/channel_mgmt.c index 25dbbaf..bb39705 100644 --- a/drivers/hv/channel_mgmt.c +++ b/drivers/hv/channel_mgmt.c @@ -884,7 +884,6 @@ int vmbus_request_offers(void) struct vmbus_channel_message_header *msg; struct vmbus_channel_msginfo *msginfo; int ret; - unsigned long t; msginfo = kmalloc(sizeof(*msginfo) + sizeof(struct vmbus_channel_message_header), @@ -892,8 +891,6 @@ int vmbus_request_offers(void) if (!msginfo) return -ENOMEM; - init_completion(&msginfo->waitevent); - msg = (struct vmbus_channel_message_header *)msginfo->msg; msg->msgtype = CHANNELMSG_REQUESTOFFERS; @@ -907,14 +904,6 @@ int vmbus_request_offers(void) goto cleanup; } - t = wait_for_completion_timeout(&msginfo->waitevent, 5*HZ); - if (t == 0) { - ret = -ETIMEDOUT; - goto cleanup; - } - - - cleanup: kfree(msginfo); -- cgit v0.10.2 From 03515f323c3766f0a69b62743794577bbd9f87c9 Mon Sep 17 00:00:00 2001 From: Nicolas Ferre Date: Thu, 19 Mar 2015 10:49:42 +0100 Subject: MAINTAINERS: change Atmel ssc driver entry I take over the maintainship from Voice. Signed-off-by: Nicolas Ferre Cc: Mark Brown Cc: Arnd Bergmann Acked-by: Bo Shen Signed-off-by: Greg Kroah-Hartman diff --git a/MAINTAINERS b/MAINTAINERS index 358eb01..d749002 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1819,7 +1819,7 @@ S: Supported F: drivers/spi/spi-atmel.* ATMEL SSC DRIVER -M: Bo Shen +M: Nicolas Ferre L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers) S: Supported F: drivers/misc/atmel-ssc.c -- cgit v0.10.2 From 0b9641f5722a9c08cacb534d633ff469ab02a288 Mon Sep 17 00:00:00 2001 From: Gilad Avidov Date: Wed, 25 Mar 2015 11:37:31 -0600 Subject: spmi: remove wakeup command before slave probe According to spmi spec a slave powers up into startup state and then transitions into active state. Thus, the wakeup command is not required before calling the slave's probe. The wakeup command is only needed for slaves that are in sleep state after receiving the sleep command. Cc: galak@codeaurora.org Reviewed-by: Stephen Boyd Reviewed-by: Sagar Dharia Acked-by: Josh Cartwright Signed-off-by: Gilad Avidov Tested-by: Ivan T. Ivanov Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/spmi/spmi.c b/drivers/spmi/spmi.c index 1d92f51..9493843 100644 --- a/drivers/spmi/spmi.c +++ b/drivers/spmi/spmi.c @@ -1,4 +1,5 @@ -/* Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. +/* + * Copyright (c) 2012-2015, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -316,11 +317,6 @@ static int spmi_drv_probe(struct device *dev) struct spmi_device *sdev = to_spmi_device(dev); int err; - /* Ensure the slave is in ACTIVE state */ - err = spmi_command_wakeup(sdev); - if (err) - goto fail_wakeup; - pm_runtime_get_noresume(dev); pm_runtime_set_active(dev); pm_runtime_enable(dev); @@ -335,7 +331,6 @@ fail_probe: pm_runtime_disable(dev); pm_runtime_set_suspended(dev); pm_runtime_put_noidle(dev); -fail_wakeup: return err; } -- cgit v0.10.2 From d0c6ae41d12ad7b2ba271f279936327320b6671c Mon Sep 17 00:00:00 2001 From: Gilad Avidov Date: Wed, 25 Mar 2015 11:37:32 -0600 Subject: spmi: pmic_arb: add support for hw version 2 Qualcomm PMIC Arbiter version-2 changes from version-1 are: - Some different register offsets. - New channel register space, one per PMIC peripheral (ppid). All tx traffic uses these channels. - New observer register space. All rx trafic uses this space. - Different command format for spmi command registers. Reviewed-by: Sagar Dharia Signed-off-by: Gilad Avidov Tested-by: Ivan T. Ivanov Signed-off-by: Greg Kroah-Hartman diff --git a/Documentation/devicetree/bindings/spmi/qcom,spmi-pmic-arb.txt b/Documentation/devicetree/bindings/spmi/qcom,spmi-pmic-arb.txt index 715d099..e16b9b5 100644 --- a/Documentation/devicetree/bindings/spmi/qcom,spmi-pmic-arb.txt +++ b/Documentation/devicetree/bindings/spmi/qcom,spmi-pmic-arb.txt @@ -1,6 +1,6 @@ Qualcomm SPMI Controller (PMIC Arbiter) -The SPMI PMIC Arbiter is found on the Snapdragon 800 Series. It is an SPMI +The SPMI PMIC Arbiter is found on Snapdragon chipsets. It is an SPMI controller with wrapping arbitration logic to allow for multiple on-chip devices to control a single SPMI master. @@ -19,6 +19,10 @@ Required properties: "core" - core registers "intr" - interrupt controller registers "cnfg" - configuration registers + Registers used only for V2 PMIC Arbiter: + "chnls" - tx-channel per virtual slave registers. + "obsrvr" - rx-channel (called observer) per virtual slave registers. + - reg : address + size pairs describing the PMIC arb register sets; order must correspond with the order of entries in reg-names - #address-cells : must be set to 2 diff --git a/drivers/spmi/spmi-pmic-arb.c b/drivers/spmi/spmi-pmic-arb.c index 20559ab..d7119db 100644 --- a/drivers/spmi/spmi-pmic-arb.c +++ b/drivers/spmi/spmi-pmic-arb.c @@ -1,4 +1,5 @@ -/* Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. +/* + * Copyright (c) 2012-2015, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -25,22 +26,18 @@ /* PMIC Arbiter configuration registers */ #define PMIC_ARB_VERSION 0x0000 +#define PMIC_ARB_VERSION_V2_MIN 0x20010000 #define PMIC_ARB_INT_EN 0x0004 -/* PMIC Arbiter channel registers */ -#define PMIC_ARB_CMD(N) (0x0800 + (0x80 * (N))) -#define PMIC_ARB_CONFIG(N) (0x0804 + (0x80 * (N))) -#define PMIC_ARB_STATUS(N) (0x0808 + (0x80 * (N))) -#define PMIC_ARB_WDATA0(N) (0x0810 + (0x80 * (N))) -#define PMIC_ARB_WDATA1(N) (0x0814 + (0x80 * (N))) -#define PMIC_ARB_RDATA0(N) (0x0818 + (0x80 * (N))) -#define PMIC_ARB_RDATA1(N) (0x081C + (0x80 * (N))) - -/* Interrupt Controller */ -#define SPMI_PIC_OWNER_ACC_STATUS(M, N) (0x0000 + ((32 * (M)) + (4 * (N)))) -#define SPMI_PIC_ACC_ENABLE(N) (0x0200 + (4 * (N))) -#define SPMI_PIC_IRQ_STATUS(N) (0x0600 + (4 * (N))) -#define SPMI_PIC_IRQ_CLEAR(N) (0x0A00 + (4 * (N))) +/* PMIC Arbiter channel registers offsets */ +#define PMIC_ARB_CMD 0x00 +#define PMIC_ARB_CONFIG 0x04 +#define PMIC_ARB_STATUS 0x08 +#define PMIC_ARB_WDATA0 0x10 +#define PMIC_ARB_WDATA1 0x14 +#define PMIC_ARB_RDATA0 0x18 +#define PMIC_ARB_RDATA1 0x1C +#define PMIC_ARB_REG_CHNL(N) (0x800 + 0x4 * (N)) /* Mapping Table */ #define SPMI_MAPPING_TABLE_REG(N) (0x0B00 + (4 * (N))) @@ -52,6 +49,7 @@ #define SPMI_MAPPING_TABLE_LEN 255 #define SPMI_MAPPING_TABLE_TREE_DEPTH 16 /* Maximum of 16-bits */ +#define PPID_TO_CHAN_TABLE_SZ BIT(12) /* PPID is 12bit chan is 1byte*/ /* Ownership Table */ #define SPMI_OWNERSHIP_TABLE_REG(N) (0x0700 + (4 * (N))) @@ -88,6 +86,7 @@ enum pmic_arb_cmd_op_code { /* Maximum number of support PMIC peripherals */ #define PMIC_ARB_MAX_PERIPHS 256 +#define PMIC_ARB_MAX_CHNL 128 #define PMIC_ARB_PERIPH_ID_VALID (1 << 15) #define PMIC_ARB_TIMEOUT_US 100 #define PMIC_ARB_MAX_TRANS_BYTES (8) @@ -98,14 +97,17 @@ enum pmic_arb_cmd_op_code { /* interrupt enable bit */ #define SPMI_PIC_ACC_ENABLE_BIT BIT(0) +struct pmic_arb_ver_ops; + /** * spmi_pmic_arb_dev - SPMI PMIC Arbiter object * - * @base: address of the PMIC Arbiter core registers. + * @rd_base: on v1 "core", on v2 "observer" register base off DT. + * @wr_base: on v1 "core", on v2 "chnls" register base off DT. * @intr: address of the SPMI interrupt control registers. * @cnfg: address of the PMIC Arbiter configuration registers. * @lock: lock to synchronize accesses. - * @channel: which channel to use for accesses. + * @channel: execution environment channel to use for accesses. * @irq: PMIC ARB interrupt. * @ee: the current Execution Environment * @min_apid: minimum APID (used for bounding IRQ search) @@ -113,10 +115,14 @@ enum pmic_arb_cmd_op_code { * @mapping_table: in-memory copy of PPID -> APID mapping table. * @domain: irq domain object for PMIC IRQ domain * @spmic: SPMI controller object - * @apid_to_ppid: cached mapping from APID to PPID + * @apid_to_ppid: in-memory copy of APID -> PPID mapping table. + * @ver_ops: version dependent operations. + * @ppid_to_chan in-memory copy of PPID -> channel (APID) mapping table. + * v2 only. */ struct spmi_pmic_arb_dev { - void __iomem *base; + void __iomem *rd_base; + void __iomem *wr_base; void __iomem *intr; void __iomem *cnfg; raw_spinlock_t lock; @@ -129,17 +135,54 @@ struct spmi_pmic_arb_dev { struct irq_domain *domain; struct spmi_controller *spmic; u16 apid_to_ppid[256]; + const struct pmic_arb_ver_ops *ver_ops; + u8 *ppid_to_chan; +}; + +/** + * pmic_arb_ver: version dependent functionality. + * + * @non_data_cmd: on v1 issues an spmi non-data command. + * on v2 no HW support, returns -EOPNOTSUPP. + * @offset: on v1 offset of per-ee channel. + * on v2 offset of per-ee and per-ppid channel. + * @fmt_cmd: formats a GENI/SPMI command. + * @owner_acc_status: on v1 offset of PMIC_ARB_SPMI_PIC_OWNERm_ACC_STATUSn + * on v2 offset of SPMI_PIC_OWNERm_ACC_STATUSn. + * @acc_enable: on v1 offset of PMIC_ARB_SPMI_PIC_ACC_ENABLEn + * on v2 offset of SPMI_PIC_ACC_ENABLEn. + * @irq_status: on v1 offset of PMIC_ARB_SPMI_PIC_IRQ_STATUSn + * on v2 offset of SPMI_PIC_IRQ_STATUSn. + * @irq_clear: on v1 offset of PMIC_ARB_SPMI_PIC_IRQ_CLEARn + * on v2 offset of SPMI_PIC_IRQ_CLEARn. + */ +struct pmic_arb_ver_ops { + /* spmi commands (read_cmd, write_cmd, cmd) functionality */ + u32 (*offset)(struct spmi_pmic_arb_dev *dev, u8 sid, u16 addr); + u32 (*fmt_cmd)(u8 opc, u8 sid, u16 addr, u8 bc); + int (*non_data_cmd)(struct spmi_controller *ctrl, u8 opc, u8 sid); + /* Interrupts controller functionality (offset of PIC registers) */ + u32 (*owner_acc_status)(u8 m, u8 n); + u32 (*acc_enable)(u8 n); + u32 (*irq_status)(u8 n); + u32 (*irq_clear)(u8 n); }; static inline u32 pmic_arb_base_read(struct spmi_pmic_arb_dev *dev, u32 offset) { - return readl_relaxed(dev->base + offset); + return readl_relaxed(dev->rd_base + offset); } static inline void pmic_arb_base_write(struct spmi_pmic_arb_dev *dev, u32 offset, u32 val) { - writel_relaxed(val, dev->base + offset); + writel_relaxed(val, dev->wr_base + offset); +} + +static inline void pmic_arb_set_rd_cmd(struct spmi_pmic_arb_dev *dev, + u32 offset, u32 val) +{ + writel_relaxed(val, dev->rd_base + offset); } /** @@ -168,15 +211,16 @@ pa_write_data(struct spmi_pmic_arb_dev *dev, const u8 *buf, u32 reg, u8 bc) pmic_arb_base_write(dev, reg, data); } -static int pmic_arb_wait_for_done(struct spmi_controller *ctrl) +static int pmic_arb_wait_for_done(struct spmi_controller *ctrl, + void __iomem *base, u8 sid, u16 addr) { struct spmi_pmic_arb_dev *dev = spmi_controller_get_drvdata(ctrl); u32 status = 0; u32 timeout = PMIC_ARB_TIMEOUT_US; - u32 offset = PMIC_ARB_STATUS(dev->channel); + u32 offset = dev->ver_ops->offset(dev, sid, addr) + PMIC_ARB_STATUS; while (timeout--) { - status = pmic_arb_base_read(dev, offset); + status = readl_relaxed(base + offset); if (status & PMIC_ARB_STATUS_DONE) { if (status & PMIC_ARB_STATUS_DENIED) { @@ -211,28 +255,45 @@ static int pmic_arb_wait_for_done(struct spmi_controller *ctrl) return -ETIMEDOUT; } -/* Non-data command */ -static int pmic_arb_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid) +static int +pmic_arb_non_data_cmd_v1(struct spmi_controller *ctrl, u8 opc, u8 sid) { struct spmi_pmic_arb_dev *pmic_arb = spmi_controller_get_drvdata(ctrl); unsigned long flags; u32 cmd; int rc; - - /* Check for valid non-data command */ - if (opc < SPMI_CMD_RESET || opc > SPMI_CMD_WAKEUP) - return -EINVAL; + u32 offset = pmic_arb->ver_ops->offset(pmic_arb, sid, 0); cmd = ((opc | 0x40) << 27) | ((sid & 0xf) << 20); raw_spin_lock_irqsave(&pmic_arb->lock, flags); - pmic_arb_base_write(pmic_arb, PMIC_ARB_CMD(pmic_arb->channel), cmd); - rc = pmic_arb_wait_for_done(ctrl); + pmic_arb_base_write(pmic_arb, offset + PMIC_ARB_CMD, cmd); + rc = pmic_arb_wait_for_done(ctrl, pmic_arb->wr_base, sid, 0); raw_spin_unlock_irqrestore(&pmic_arb->lock, flags); return rc; } +static int +pmic_arb_non_data_cmd_v2(struct spmi_controller *ctrl, u8 opc, u8 sid) +{ + return -EOPNOTSUPP; +} + +/* Non-data command */ +static int pmic_arb_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid) +{ + struct spmi_pmic_arb_dev *pmic_arb = spmi_controller_get_drvdata(ctrl); + + dev_dbg(&ctrl->dev, "cmd op:0x%x sid:%d\n", opc, sid); + + /* Check for valid non-data command */ + if (opc < SPMI_CMD_RESET || opc > SPMI_CMD_WAKEUP) + return -EINVAL; + + return pmic_arb->ver_ops->non_data_cmd(ctrl, opc, sid); +} + static int pmic_arb_read_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, u16 addr, u8 *buf, size_t len) { @@ -241,10 +302,11 @@ static int pmic_arb_read_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, u8 bc = len - 1; u32 cmd; int rc; + u32 offset = pmic_arb->ver_ops->offset(pmic_arb, sid, addr); if (bc >= PMIC_ARB_MAX_TRANS_BYTES) { dev_err(&ctrl->dev, - "pmic-arb supports 1..%d bytes per trans, but %d requested", + "pmic-arb supports 1..%d bytes per trans, but:%zu requested", PMIC_ARB_MAX_TRANS_BYTES, len); return -EINVAL; } @@ -259,20 +321,20 @@ static int pmic_arb_read_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, else return -EINVAL; - cmd = (opc << 27) | ((sid & 0xf) << 20) | (addr << 4) | (bc & 0x7); + cmd = pmic_arb->ver_ops->fmt_cmd(opc, sid, addr, bc); raw_spin_lock_irqsave(&pmic_arb->lock, flags); - pmic_arb_base_write(pmic_arb, PMIC_ARB_CMD(pmic_arb->channel), cmd); - rc = pmic_arb_wait_for_done(ctrl); + pmic_arb_set_rd_cmd(pmic_arb, offset + PMIC_ARB_CMD, cmd); + rc = pmic_arb_wait_for_done(ctrl, pmic_arb->rd_base, sid, addr); if (rc) goto done; - pa_read_data(pmic_arb, buf, PMIC_ARB_RDATA0(pmic_arb->channel), + pa_read_data(pmic_arb, buf, offset + PMIC_ARB_RDATA0, min_t(u8, bc, 3)); if (bc > 3) pa_read_data(pmic_arb, buf + 4, - PMIC_ARB_RDATA1(pmic_arb->channel), bc - 4); + offset + PMIC_ARB_RDATA1, bc - 4); done: raw_spin_unlock_irqrestore(&pmic_arb->lock, flags); @@ -287,10 +349,11 @@ static int pmic_arb_write_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, u8 bc = len - 1; u32 cmd; int rc; + u32 offset = pmic_arb->ver_ops->offset(pmic_arb, sid, addr); if (bc >= PMIC_ARB_MAX_TRANS_BYTES) { dev_err(&ctrl->dev, - "pmic-arb supports 1..%d bytes per trans, but:%d requested", + "pmic-arb supports 1..%d bytes per trans, but:%zu requested", PMIC_ARB_MAX_TRANS_BYTES, len); return -EINVAL; } @@ -307,19 +370,19 @@ static int pmic_arb_write_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, else return -EINVAL; - cmd = (opc << 27) | ((sid & 0xf) << 20) | (addr << 4) | (bc & 0x7); + cmd = pmic_arb->ver_ops->fmt_cmd(opc, sid, addr, bc); /* Write data to FIFOs */ raw_spin_lock_irqsave(&pmic_arb->lock, flags); - pa_write_data(pmic_arb, buf, PMIC_ARB_WDATA0(pmic_arb->channel) - , min_t(u8, bc, 3)); + pa_write_data(pmic_arb, buf, offset + PMIC_ARB_WDATA0, + min_t(u8, bc, 3)); if (bc > 3) pa_write_data(pmic_arb, buf + 4, - PMIC_ARB_WDATA1(pmic_arb->channel), bc - 4); + offset + PMIC_ARB_WDATA1, bc - 4); /* Start the transaction */ - pmic_arb_base_write(pmic_arb, PMIC_ARB_CMD(pmic_arb->channel), cmd); - rc = pmic_arb_wait_for_done(ctrl); + pmic_arb_base_write(pmic_arb, offset + PMIC_ARB_CMD, cmd); + rc = pmic_arb_wait_for_done(ctrl, pmic_arb->wr_base, sid, addr); raw_spin_unlock_irqrestore(&pmic_arb->lock, flags); return rc; @@ -376,7 +439,7 @@ static void periph_interrupt(struct spmi_pmic_arb_dev *pa, u8 apid) u32 status; int id; - status = readl_relaxed(pa->intr + SPMI_PIC_IRQ_STATUS(apid)); + status = readl_relaxed(pa->intr + pa->ver_ops->irq_status(apid)); while (status) { id = ffs(status) - 1; status &= ~(1 << id); @@ -402,7 +465,7 @@ static void pmic_arb_chained_irq(unsigned int irq, struct irq_desc *desc) for (i = first; i <= last; ++i) { status = readl_relaxed(intr + - SPMI_PIC_OWNER_ACC_STATUS(pa->ee, i)); + pa->ver_ops->owner_acc_status(pa->ee, i)); while (status) { id = ffs(status) - 1; status &= ~(1 << id); @@ -422,7 +485,7 @@ static void qpnpint_irq_ack(struct irq_data *d) u8 data; raw_spin_lock_irqsave(&pa->lock, flags); - writel_relaxed(1 << irq, pa->intr + SPMI_PIC_IRQ_CLEAR(apid)); + writel_relaxed(1 << irq, pa->intr + pa->ver_ops->irq_clear(apid)); raw_spin_unlock_irqrestore(&pa->lock, flags); data = 1 << irq; @@ -439,10 +502,11 @@ static void qpnpint_irq_mask(struct irq_data *d) u8 data; raw_spin_lock_irqsave(&pa->lock, flags); - status = readl_relaxed(pa->intr + SPMI_PIC_ACC_ENABLE(apid)); + status = readl_relaxed(pa->intr + pa->ver_ops->acc_enable(apid)); if (status & SPMI_PIC_ACC_ENABLE_BIT) { status = status & ~SPMI_PIC_ACC_ENABLE_BIT; - writel_relaxed(status, pa->intr + SPMI_PIC_ACC_ENABLE(apid)); + writel_relaxed(status, pa->intr + + pa->ver_ops->acc_enable(apid)); } raw_spin_unlock_irqrestore(&pa->lock, flags); @@ -460,10 +524,10 @@ static void qpnpint_irq_unmask(struct irq_data *d) u8 data; raw_spin_lock_irqsave(&pa->lock, flags); - status = readl_relaxed(pa->intr + SPMI_PIC_ACC_ENABLE(apid)); + status = readl_relaxed(pa->intr + pa->ver_ops->acc_enable(apid)); if (!(status & SPMI_PIC_ACC_ENABLE_BIT)) { writel_relaxed(status | SPMI_PIC_ACC_ENABLE_BIT, - pa->intr + SPMI_PIC_ACC_ENABLE(apid)); + pa->intr + pa->ver_ops->acc_enable(apid)); } raw_spin_unlock_irqrestore(&pa->lock, flags); @@ -624,6 +688,91 @@ static int qpnpint_irq_domain_map(struct irq_domain *d, return 0; } +/* v1 offset per ee */ +static u32 pmic_arb_offset_v1(struct spmi_pmic_arb_dev *pa, u8 sid, u16 addr) +{ + return 0x800 + 0x80 * pa->channel; +} + +/* v2 offset per ppid (chan) and per ee */ +static u32 pmic_arb_offset_v2(struct spmi_pmic_arb_dev *pa, u8 sid, u16 addr) +{ + u16 ppid = (sid << 8) | (addr >> 8); + u8 chan = pa->ppid_to_chan[ppid]; + + return 0x1000 * pa->ee + 0x8000 * chan; +} + +static u32 pmic_arb_fmt_cmd_v1(u8 opc, u8 sid, u16 addr, u8 bc) +{ + return (opc << 27) | ((sid & 0xf) << 20) | (addr << 4) | (bc & 0x7); +} + +static u32 pmic_arb_fmt_cmd_v2(u8 opc, u8 sid, u16 addr, u8 bc) +{ + return (opc << 27) | ((addr & 0xff) << 4) | (bc & 0x7); +} + +static u32 pmic_arb_owner_acc_status_v1(u8 m, u8 n) +{ + return 0x20 * m + 0x4 * n; +} + +static u32 pmic_arb_owner_acc_status_v2(u8 m, u8 n) +{ + return 0x100000 + 0x1000 * m + 0x4 * n; +} + +static u32 pmic_arb_acc_enable_v1(u8 n) +{ + return 0x200 + 0x4 * n; +} + +static u32 pmic_arb_acc_enable_v2(u8 n) +{ + return 0x1000 * n; +} + +static u32 pmic_arb_irq_status_v1(u8 n) +{ + return 0x600 + 0x4 * n; +} + +static u32 pmic_arb_irq_status_v2(u8 n) +{ + return 0x4 + 0x1000 * n; +} + +static u32 pmic_arb_irq_clear_v1(u8 n) +{ + return 0xA00 + 0x4 * n; +} + +static u32 pmic_arb_irq_clear_v2(u8 n) +{ + return 0x8 + 0x1000 * n; +} + +static const struct pmic_arb_ver_ops pmic_arb_v1 = { + .non_data_cmd = pmic_arb_non_data_cmd_v1, + .offset = pmic_arb_offset_v1, + .fmt_cmd = pmic_arb_fmt_cmd_v1, + .owner_acc_status = pmic_arb_owner_acc_status_v1, + .acc_enable = pmic_arb_acc_enable_v1, + .irq_status = pmic_arb_irq_status_v1, + .irq_clear = pmic_arb_irq_clear_v1, +}; + +static const struct pmic_arb_ver_ops pmic_arb_v2 = { + .non_data_cmd = pmic_arb_non_data_cmd_v2, + .offset = pmic_arb_offset_v2, + .fmt_cmd = pmic_arb_fmt_cmd_v2, + .owner_acc_status = pmic_arb_owner_acc_status_v2, + .acc_enable = pmic_arb_acc_enable_v2, + .irq_status = pmic_arb_irq_status_v2, + .irq_clear = pmic_arb_irq_clear_v2, +}; + static const struct irq_domain_ops pmic_arb_irq_domain_ops = { .map = qpnpint_irq_domain_map, .xlate = qpnpint_irq_domain_dt_translate, @@ -634,8 +783,10 @@ static int spmi_pmic_arb_probe(struct platform_device *pdev) struct spmi_pmic_arb_dev *pa; struct spmi_controller *ctrl; struct resource *res; - u32 channel, ee; + void __iomem *core; + u32 channel, ee, hw_ver; int err, i; + bool is_v1; ctrl = spmi_controller_alloc(&pdev->dev, sizeof(*pa)); if (!ctrl) @@ -645,12 +796,65 @@ static int spmi_pmic_arb_probe(struct platform_device *pdev) pa->spmic = ctrl; res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "core"); - pa->base = devm_ioremap_resource(&ctrl->dev, res); - if (IS_ERR(pa->base)) { - err = PTR_ERR(pa->base); + core = devm_ioremap_resource(&ctrl->dev, res); + if (IS_ERR(core)) { + err = PTR_ERR(core); goto err_put_ctrl; } + hw_ver = readl_relaxed(core + PMIC_ARB_VERSION); + is_v1 = (hw_ver < PMIC_ARB_VERSION_V2_MIN); + + dev_info(&ctrl->dev, "PMIC Arb Version-%d (0x%x)\n", (is_v1 ? 1 : 2), + hw_ver); + + if (is_v1) { + pa->ver_ops = &pmic_arb_v1; + pa->wr_base = core; + pa->rd_base = core; + } else { + u8 chan; + u16 ppid; + u32 regval; + + pa->ver_ops = &pmic_arb_v2; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "obsrvr"); + pa->rd_base = devm_ioremap_resource(&ctrl->dev, res); + if (IS_ERR(pa->rd_base)) { + err = PTR_ERR(pa->rd_base); + goto err_put_ctrl; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "chnls"); + pa->wr_base = devm_ioremap_resource(&ctrl->dev, res); + if (IS_ERR(pa->wr_base)) { + err = PTR_ERR(pa->wr_base); + goto err_put_ctrl; + } + + pa->ppid_to_chan = devm_kzalloc(&ctrl->dev, + PPID_TO_CHAN_TABLE_SZ, GFP_KERNEL); + if (!pa->ppid_to_chan) { + err = -ENOMEM; + goto err_put_ctrl; + } + /* + * PMIC_ARB_REG_CHNL is a table in HW mapping channel to ppid. + * ppid_to_chan is an in-memory invert of that table. + */ + for (chan = 0; chan < PMIC_ARB_MAX_CHNL; ++chan) { + regval = readl_relaxed(core + PMIC_ARB_REG_CHNL(chan)); + if (!regval) + continue; + + ppid = (regval >> 8) & 0xFFF; + pa->ppid_to_chan[ppid] = chan; + } + } + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "intr"); pa->intr = devm_ioremap_resource(&ctrl->dev, res); if (IS_ERR(pa->intr)) { @@ -731,9 +935,6 @@ static int spmi_pmic_arb_probe(struct platform_device *pdev) if (err) goto err_domain_remove; - dev_dbg(&ctrl->dev, "PMIC Arb Version 0x%x\n", - pmic_arb_base_read(pa, PMIC_ARB_VERSION)); - return 0; err_domain_remove: -- cgit v0.10.2 From c0e6841653e9e96fea9e0f973a785bc66f45b532 Mon Sep 17 00:00:00 2001 From: Alex Smith Date: Mon, 9 Mar 2015 14:29:03 +0000 Subject: dt-bindings: memory-controllers: Add binding for jz4780-nemc Add device tree bindings for the NAND/External Memory Controller (NEMC) on Ingenic JZ4780 Signed-off-by: Alex Smith Signed-off-by: Zubair Lutfullah Kakakhel Signed-off-by: Greg Kroah-Hartman diff --git a/Documentation/devicetree/bindings/memory-controllers/ingenic,jz4780-nemc.txt b/Documentation/devicetree/bindings/memory-controllers/ingenic,jz4780-nemc.txt new file mode 100644 index 0000000..f936b55 --- /dev/null +++ b/Documentation/devicetree/bindings/memory-controllers/ingenic,jz4780-nemc.txt @@ -0,0 +1,75 @@ +* Ingenic JZ4780 NAND/external memory controller (NEMC) + +This file documents the device tree bindings for the NEMC external memory +controller in Ingenic JZ4780 + +Required properties: +- compatible: Should be set to one of: + "ingenic,jz4780-nemc" (JZ4780) +- reg: Should specify the NEMC controller registers location and length. +- clocks: Clock for the NEMC controller. +- #address-cells: Must be set to 2. +- #size-cells: Must be set to 1. +- ranges: A set of ranges for each bank describing the physical memory layout. + Each should specify the following 4 integer values: + + 0 + +Each child of the NEMC node describes a device connected to the NEMC. + +Required child node properties: +- reg: Should contain at least one register specifier, given in the following + format: + + + + Multiple registers can be specified across multiple banks. This is needed, + for example, for packaged NAND devices with multiple dies. Such devices + should be grouped into a single node. + +Optional child node properties: +- ingenic,nemc-bus-width: Specifies the bus width in bits. Defaults to 8 bits. +- ingenic,nemc-tAS: Address setup time in nanoseconds. +- ingenic,nemc-tAH: Address hold time in nanoseconds. +- ingenic,nemc-tBP: Burst pitch time in nanoseconds. +- ingenic,nemc-tAW: Access wait time in nanoseconds. +- ingenic,nemc-tSTRV: Static memory recovery time in nanoseconds. + +If a child node references multiple banks in its "reg" property, the same value +for all optional parameters will be configured for all banks. If any optional +parameters are omitted, they will be left unchanged from whatever they are +configured to when the NEMC device is probed (which may be the reset value as +given in the hardware reference manual, or a value configured by the boot +loader). + +Example (NEMC node with a NAND child device attached at CS1): + +nemc: nemc@13410000 { + compatible = "ingenic,jz4780-nemc"; + reg = <0x13410000 0x10000>; + + #address-cells = <2>; + #size-cells = <1>; + + ranges = <1 0 0x1b000000 0x1000000 + 2 0 0x1a000000 0x1000000 + 3 0 0x19000000 0x1000000 + 4 0 0x18000000 0x1000000 + 5 0 0x17000000 0x1000000 + 6 0 0x16000000 0x1000000>; + + clocks = <&cgu JZ4780_CLK_NEMC>; + + nand: nand@1 { + compatible = "ingenic,jz4780-nand"; + reg = <1 0 0x1000000>; + + ingenic,nemc-tAS = <10>; + ingenic,nemc-tAH = <5>; + ingenic,nemc-tBP = <10>; + ingenic,nemc-tAW = <15>; + ingenic,nemc-tSTRV = <100>; + + ... + }; +}; -- cgit v0.10.2 From 911a88829725572820dad9a168e735c606a2fdcb Mon Sep 17 00:00:00 2001 From: Alex Smith Date: Mon, 9 Mar 2015 14:29:04 +0000 Subject: memory: jz4780-nemc: driver for the NEMC on JZ4780 SoCs Add a driver for the NAND/External Memory Controller (NEMC) on JZ4780 and later SoCs. The primary function of this driver is to configure parameters, such as timings, for external memory devices using data supplied in the device tree. Devices connected to the NEMC are represented in the DT as children of the NEMC node, the driver uses optional properties specified in these child nodes to configure the parameters of each bank. Signed-off-by: Alex Smith Signed-off-by: Zubair Lutfullah Kakakhel Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/memory/Kconfig b/drivers/memory/Kconfig index 191383d8..868036f 100644 --- a/drivers/memory/Kconfig +++ b/drivers/memory/Kconfig @@ -83,6 +83,15 @@ config FSL_IFC bool depends on FSL_SOC +config JZ4780_NEMC + bool "Ingenic JZ4780 SoC NEMC driver" + default y + depends on MACH_JZ4780 + help + This driver is for the NAND/External Memory Controller (NEMC) in + the Ingenic JZ4780. This controller is used to handle external + memory devices such as NAND and SRAM. + source "drivers/memory/tegra/Kconfig" endif diff --git a/drivers/memory/Makefile b/drivers/memory/Makefile index 6b65481..b670441 100644 --- a/drivers/memory/Makefile +++ b/drivers/memory/Makefile @@ -13,5 +13,6 @@ obj-$(CONFIG_FSL_CORENET_CF) += fsl-corenet-cf.o obj-$(CONFIG_FSL_IFC) += fsl_ifc.o obj-$(CONFIG_MVEBU_DEVBUS) += mvebu-devbus.o obj-$(CONFIG_TEGRA20_MC) += tegra20-mc.o +obj-$(CONFIG_JZ4780_NEMC) += jz4780-nemc.o obj-$(CONFIG_TEGRA_MC) += tegra/ diff --git a/drivers/memory/jz4780-nemc.c b/drivers/memory/jz4780-nemc.c new file mode 100644 index 0000000..919d192 --- /dev/null +++ b/drivers/memory/jz4780-nemc.c @@ -0,0 +1,391 @@ +/* + * JZ4780 NAND/external memory controller (NEMC) + * + * Copyright (c) 2015 Imagination Technologies + * Author: Alex Smith + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define NEMC_SMCRn(n) (0x14 + (((n) - 1) * 4)) +#define NEMC_NFCSR 0x50 + +#define NEMC_SMCR_SMT BIT(0) +#define NEMC_SMCR_BW_SHIFT 6 +#define NEMC_SMCR_BW_MASK (0x3 << NEMC_SMCR_BW_SHIFT) +#define NEMC_SMCR_BW_8 (0 << 6) +#define NEMC_SMCR_TAS_SHIFT 8 +#define NEMC_SMCR_TAS_MASK (0xf << NEMC_SMCR_TAS_SHIFT) +#define NEMC_SMCR_TAH_SHIFT 12 +#define NEMC_SMCR_TAH_MASK (0xf << NEMC_SMCR_TAH_SHIFT) +#define NEMC_SMCR_TBP_SHIFT 16 +#define NEMC_SMCR_TBP_MASK (0xf << NEMC_SMCR_TBP_SHIFT) +#define NEMC_SMCR_TAW_SHIFT 20 +#define NEMC_SMCR_TAW_MASK (0xf << NEMC_SMCR_TAW_SHIFT) +#define NEMC_SMCR_TSTRV_SHIFT 24 +#define NEMC_SMCR_TSTRV_MASK (0x3f << NEMC_SMCR_TSTRV_SHIFT) + +#define NEMC_NFCSR_NFEn(n) BIT(((n) - 1) << 1) +#define NEMC_NFCSR_NFCEn(n) BIT((((n) - 1) << 1) + 1) +#define NEMC_NFCSR_TNFEn(n) BIT(16 + (n) - 1) + +struct jz4780_nemc { + spinlock_t lock; + struct device *dev; + void __iomem *base; + struct clk *clk; + uint32_t clk_period; + unsigned long banks_present; +}; + +/** + * jz4780_nemc_num_banks() - count the number of banks referenced by a device + * @dev: device to count banks for, must be a child of the NEMC. + * + * Return: The number of unique NEMC banks referred to by the specified NEMC + * child device. Unique here means that a device that references the same bank + * multiple times in the its "reg" property will only count once. + */ +unsigned int jz4780_nemc_num_banks(struct device *dev) +{ + const __be32 *prop; + unsigned int bank, count = 0; + unsigned long referenced = 0; + int i = 0; + + while ((prop = of_get_address(dev->of_node, i++, NULL, NULL))) { + bank = of_read_number(prop, 1); + if (!(referenced & BIT(bank))) { + referenced |= BIT(bank); + count++; + } + } + + return count; +} +EXPORT_SYMBOL(jz4780_nemc_num_banks); + +/** + * jz4780_nemc_set_type() - set the type of device connected to a bank + * @dev: child device of the NEMC. + * @bank: bank number to configure. + * @type: type of device connected to the bank. + */ +void jz4780_nemc_set_type(struct device *dev, unsigned int bank, + enum jz4780_nemc_bank_type type) +{ + struct jz4780_nemc *nemc = dev_get_drvdata(dev->parent); + uint32_t nfcsr; + + nfcsr = readl(nemc->base + NEMC_NFCSR); + + /* TODO: Support toggle NAND devices. */ + switch (type) { + case JZ4780_NEMC_BANK_SRAM: + nfcsr &= ~(NEMC_NFCSR_TNFEn(bank) | NEMC_NFCSR_NFEn(bank)); + break; + case JZ4780_NEMC_BANK_NAND: + nfcsr &= ~NEMC_NFCSR_TNFEn(bank); + nfcsr |= NEMC_NFCSR_NFEn(bank); + break; + } + + writel(nfcsr, nemc->base + NEMC_NFCSR); +} +EXPORT_SYMBOL(jz4780_nemc_set_type); + +/** + * jz4780_nemc_assert() - (de-)assert a NAND device's chip enable pin + * @dev: child device of the NEMC. + * @bank: bank number of device. + * @assert: whether the chip enable pin should be asserted. + * + * (De-)asserts the chip enable pin for the NAND device connected to the + * specified bank. + */ +void jz4780_nemc_assert(struct device *dev, unsigned int bank, bool assert) +{ + struct jz4780_nemc *nemc = dev_get_drvdata(dev->parent); + uint32_t nfcsr; + + nfcsr = readl(nemc->base + NEMC_NFCSR); + + if (assert) + nfcsr |= NEMC_NFCSR_NFCEn(bank); + else + nfcsr &= ~NEMC_NFCSR_NFCEn(bank); + + writel(nfcsr, nemc->base + NEMC_NFCSR); +} +EXPORT_SYMBOL(jz4780_nemc_assert); + +static uint32_t jz4780_nemc_clk_period(struct jz4780_nemc *nemc) +{ + unsigned long rate; + + rate = clk_get_rate(nemc->clk); + if (!rate) + return 0; + + /* Return in picoseconds. */ + return div64_ul(1000000000000ull, rate); +} + +static uint32_t jz4780_nemc_ns_to_cycles(struct jz4780_nemc *nemc, uint32_t ns) +{ + return ((ns * 1000) + nemc->clk_period - 1) / nemc->clk_period; +} + +static bool jz4780_nemc_configure_bank(struct jz4780_nemc *nemc, + unsigned int bank, + struct device_node *node) +{ + uint32_t smcr, val, cycles; + + /* + * Conversion of tBP and tAW cycle counts to values supported by the + * hardware (round up to the next supported value). + */ + static const uint32_t convert_tBP_tAW[] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + + /* 11 - 12 -> 12 cycles */ + 11, 11, + + /* 13 - 15 -> 15 cycles */ + 12, 12, 12, + + /* 16 - 20 -> 20 cycles */ + 13, 13, 13, 13, 13, + + /* 21 - 25 -> 25 cycles */ + 14, 14, 14, 14, 14, + + /* 26 - 31 -> 31 cycles */ + 15, 15, 15, 15, 15, 15 + }; + + smcr = readl(nemc->base + NEMC_SMCRn(bank)); + smcr &= ~NEMC_SMCR_SMT; + + if (!of_property_read_u32(node, "ingenic,nemc-bus-width", &val)) { + smcr &= ~NEMC_SMCR_BW_MASK; + switch (val) { + case 8: + smcr |= NEMC_SMCR_BW_8; + break; + default: + /* + * Earlier SoCs support a 16 bit bus width (the 4780 + * does not), until those are properly supported, error. + */ + dev_err(nemc->dev, "unsupported bus width: %u\n", val); + return false; + } + } + + if (of_property_read_u32(node, "ingenic,nemc-tAS", &val) == 0) { + smcr &= ~NEMC_SMCR_TAS_MASK; + cycles = jz4780_nemc_ns_to_cycles(nemc, val); + if (cycles > 15) { + dev_err(nemc->dev, "tAS %u is too high (%u cycles)\n", + val, cycles); + return false; + } + + smcr |= cycles << NEMC_SMCR_TAS_SHIFT; + } + + if (of_property_read_u32(node, "ingenic,nemc-tAH", &val) == 0) { + smcr &= ~NEMC_SMCR_TAH_MASK; + cycles = jz4780_nemc_ns_to_cycles(nemc, val); + if (cycles > 15) { + dev_err(nemc->dev, "tAH %u is too high (%u cycles)\n", + val, cycles); + return false; + } + + smcr |= cycles << NEMC_SMCR_TAH_SHIFT; + } + + if (of_property_read_u32(node, "ingenic,nemc-tBP", &val) == 0) { + smcr &= ~NEMC_SMCR_TBP_MASK; + cycles = jz4780_nemc_ns_to_cycles(nemc, val); + if (cycles > 31) { + dev_err(nemc->dev, "tBP %u is too high (%u cycles)\n", + val, cycles); + return false; + } + + smcr |= convert_tBP_tAW[cycles] << NEMC_SMCR_TBP_SHIFT; + } + + if (of_property_read_u32(node, "ingenic,nemc-tAW", &val) == 0) { + smcr &= ~NEMC_SMCR_TAW_MASK; + cycles = jz4780_nemc_ns_to_cycles(nemc, val); + if (cycles > 31) { + dev_err(nemc->dev, "tAW %u is too high (%u cycles)\n", + val, cycles); + return false; + } + + smcr |= convert_tBP_tAW[cycles] << NEMC_SMCR_TAW_SHIFT; + } + + if (of_property_read_u32(node, "ingenic,nemc-tSTRV", &val) == 0) { + smcr &= ~NEMC_SMCR_TSTRV_MASK; + cycles = jz4780_nemc_ns_to_cycles(nemc, val); + if (cycles > 63) { + dev_err(nemc->dev, "tSTRV %u is too high (%u cycles)\n", + val, cycles); + return false; + } + + smcr |= cycles << NEMC_SMCR_TSTRV_SHIFT; + } + + writel(smcr, nemc->base + NEMC_SMCRn(bank)); + return true; +} + +static int jz4780_nemc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct jz4780_nemc *nemc; + struct resource *res; + struct device_node *child; + const __be32 *prop; + unsigned int bank; + unsigned long referenced; + int i, ret; + + nemc = devm_kzalloc(dev, sizeof(*nemc), GFP_KERNEL); + if (!nemc) + return -ENOMEM; + + spin_lock_init(&nemc->lock); + nemc->dev = dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + nemc->base = devm_ioremap_resource(dev, res); + if (IS_ERR(nemc->base)) { + dev_err(dev, "failed to get I/O memory\n"); + return PTR_ERR(nemc->base); + } + + writel(0, nemc->base + NEMC_NFCSR); + + nemc->clk = devm_clk_get(dev, NULL); + if (IS_ERR(nemc->clk)) { + dev_err(dev, "failed to get clock\n"); + return PTR_ERR(nemc->clk); + } + + ret = clk_prepare_enable(nemc->clk); + if (ret) { + dev_err(dev, "failed to enable clock: %d\n", ret); + return ret; + } + + nemc->clk_period = jz4780_nemc_clk_period(nemc); + if (!nemc->clk_period) { + dev_err(dev, "failed to calculate clock period\n"); + clk_disable_unprepare(nemc->clk); + return -EINVAL; + } + + /* + * Iterate over child devices, check that they do not conflict with + * each other, and register child devices for them. If a child device + * has invalid properties, it is ignored and no platform device is + * registered for it. + */ + for_each_child_of_node(nemc->dev->of_node, child) { + referenced = 0; + i = 0; + while ((prop = of_get_address(child, i++, NULL, NULL))) { + bank = of_read_number(prop, 1); + if (bank < 1 || bank >= JZ4780_NEMC_NUM_BANKS) { + dev_err(nemc->dev, + "%s requests invalid bank %u\n", + child->full_name, bank); + + /* Will continue the outer loop below. */ + referenced = 0; + break; + } + + referenced |= BIT(bank); + } + + if (!referenced) { + dev_err(nemc->dev, "%s has no addresses\n", + child->full_name); + continue; + } else if (nemc->banks_present & referenced) { + dev_err(nemc->dev, "%s conflicts with another node\n", + child->full_name); + continue; + } + + /* Configure bank parameters. */ + for_each_set_bit(bank, &referenced, JZ4780_NEMC_NUM_BANKS) { + if (!jz4780_nemc_configure_bank(nemc, bank, child)) { + referenced = 0; + break; + } + } + + if (referenced) { + if (of_platform_device_create(child, NULL, nemc->dev)) + nemc->banks_present |= referenced; + } + } + + platform_set_drvdata(pdev, nemc); + dev_info(dev, "JZ4780 NEMC initialised\n"); + return 0; +} + +static int jz4780_nemc_remove(struct platform_device *pdev) +{ + struct jz4780_nemc *nemc = platform_get_drvdata(pdev); + + clk_disable_unprepare(nemc->clk); + return 0; +} + +static const struct of_device_id jz4780_nemc_dt_match[] = { + { .compatible = "ingenic,jz4780-nemc" }, + {}, +}; + +static struct platform_driver jz4780_nemc_driver = { + .probe = jz4780_nemc_probe, + .remove = jz4780_nemc_remove, + .driver = { + .name = "jz4780-nemc", + .of_match_table = of_match_ptr(jz4780_nemc_dt_match), + }, +}; + +static int __init jz4780_nemc_init(void) +{ + return platform_driver_register(&jz4780_nemc_driver); +} +subsys_initcall(jz4780_nemc_init); diff --git a/include/linux/jz4780-nemc.h b/include/linux/jz4780-nemc.h new file mode 100644 index 0000000..e7f1cc7 --- /dev/null +++ b/include/linux/jz4780-nemc.h @@ -0,0 +1,43 @@ +/* + * JZ4780 NAND/external memory controller (NEMC) + * + * Copyright (c) 2015 Imagination Technologies + * Author: Alex Smith + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#ifndef __LINUX_JZ4780_NEMC_H__ +#define __LINUX_JZ4780_NEMC_H__ + +#include + +struct device; + +/* + * Number of NEMC banks. Note that there are actually 6, but they are numbered + * from 1. + */ +#define JZ4780_NEMC_NUM_BANKS 7 + +/** + * enum jz4780_nemc_bank_type - device types which can be connected to a bank + * @JZ4780_NEMC_BANK_SRAM: SRAM + * @JZ4780_NEMC_BANK_NAND: NAND + */ +enum jz4780_nemc_bank_type { + JZ4780_NEMC_BANK_SRAM, + JZ4780_NEMC_BANK_NAND, +}; + +extern unsigned int jz4780_nemc_num_banks(struct device *dev); + +extern void jz4780_nemc_set_type(struct device *dev, unsigned int bank, + enum jz4780_nemc_bank_type type); +extern void jz4780_nemc_assert(struct device *dev, unsigned int bank, + bool assert); + +#endif /* __LINUX_JZ4780_NEMC_H__ */ -- cgit v0.10.2 From 16c9c8e1ae228e89b66cbc03ec6c753ee44d39bc Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Fri, 3 Apr 2015 16:04:21 +0200 Subject: Revert "uio: constify of_device_id array" This reverts commit 4d8beff2ae07fad85d723b4cdf704b05f0ed4794. It causes build warnings, and it's incorrect as we do write to this structure. Reported-by: Stephen Rothwell Cc: Fabian Frederick Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/uio/uio_pdrv_genirq.c b/drivers/uio/uio_pdrv_genirq.c index 59c172a..f598ecd 100644 --- a/drivers/uio/uio_pdrv_genirq.c +++ b/drivers/uio/uio_pdrv_genirq.c @@ -252,7 +252,7 @@ static const struct dev_pm_ops uio_pdrv_genirq_dev_pm_ops = { }; #ifdef CONFIG_OF -static const struct of_device_id uio_of_genirq_match[] = { +static struct of_device_id uio_of_genirq_match[] = { { /* This is filled with module_parm */ }, { /* Sentinel */ }, }; -- cgit v0.10.2 From 0b509d8d336eef6d622d66b3ae2a1fc3a072bf92 Mon Sep 17 00:00:00 2001 From: Tom Van Braeckel Date: Tue, 31 Mar 2015 16:39:21 +0200 Subject: misc: pass miscdevice through file's private_data Make the miscdevice accessible through the file's private_data. Previously, this was done only when an open() file operation had been registered. If no custom open() file operation was defined, private_data was set to NULL. This subtle quirk was confusing, to the point where kernel code registered *empty* file open operations to have private_data point to the misc device structure and avoid duplicating that logic. And it could easily lead to bugs, where the addition or removal of a custom open() file operation surprisingly changes the initial value of a file's private_data structure. To resolve this, we now place the miscdevice in the file's private_data member unconditionally when open() is called. Signed-off-by: Tom Van Braeckel Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/char/misc.c b/drivers/char/misc.c index 5bb3a21..9fd5a91 100644 --- a/drivers/char/misc.c +++ b/drivers/char/misc.c @@ -140,12 +140,17 @@ static int misc_open(struct inode * inode, struct file * file) goto fail; } + /* + * Place the miscdevice in the file's + * private_data so it can be used by the + * file operations, including f_op->open below + */ + file->private_data = c; + err = 0; replace_fops(file, new_fops); - if (file->f_op->open) { - file->private_data = c; + if (file->f_op->open) err = file->f_op->open(inode,file); - } fail: mutex_unlock(&misc_mtx); return err; -- cgit v0.10.2 From f580d730c90c11be0ef4fe88aff3de80845176cb Mon Sep 17 00:00:00 2001 From: Joe Perches Date: Mon, 30 Mar 2015 16:46:10 -0700 Subject: virtio_console: Use bool function return values of true/false not 1/0 Use the normal return values for bool functions Signed-off-by: Joe Perches Reviewed-by: Amit Shah Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/char/virtio_console.c b/drivers/char/virtio_console.c index 72d7028..50754d20 100644 --- a/drivers/char/virtio_console.c +++ b/drivers/char/virtio_console.c @@ -355,7 +355,7 @@ static inline bool use_multiport(struct ports_device *portdev) * early_init */ if (!portdev->vdev) - return 0; + return false; return __virtio_test_bit(portdev->vdev, VIRTIO_CONSOLE_F_MULTIPORT); } -- cgit v0.10.2 From 149cb911ae242242e5aae698710bf59e804a96e6 Mon Sep 17 00:00:00 2001 From: "Ivan T. Ivanov" Date: Wed, 1 Apr 2015 10:46:19 +0300 Subject: spmi: pmic_arb: remove ARM build time dependency Qualcomm PMIC arbiter driver already depends on ARCH_QCOM, which could be either ARM or ARM64. New version of the PMIC arbiter controller is available on 64 bit platforms. Remove ARM dependency to allow driver to be build for 64 bit platforms. Signed-off-by: Ivan T. Ivanov Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/spmi/Kconfig b/drivers/spmi/Kconfig index bf1295e..c8d9956 100644 --- a/drivers/spmi/Kconfig +++ b/drivers/spmi/Kconfig @@ -12,7 +12,6 @@ if SPMI config SPMI_MSM_PMIC_ARB tristate "Qualcomm MSM SPMI Controller (PMIC Arbiter)" - depends on ARM depends on IRQ_DOMAIN depends on ARCH_QCOM || COMPILE_TEST default ARCH_QCOM -- cgit v0.10.2 From be29bc2eaa5360537df422a237c889dc493492f4 Mon Sep 17 00:00:00 2001 From: "Michael S. Tsirkin" Date: Sun, 29 Mar 2015 17:04:27 +0200 Subject: mic: drop pci_msi_off call on probe pci core now disables msi on probe automatically, drop this from device-specific code. Cc: Bjorn Helgaas Cc: linux-pci@vger.kernel.org Signed-off-by: Michael S. Tsirkin Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/misc/mic/host/mic_intr.c b/drivers/misc/mic/host/mic_intr.c index d686f28..b4ca6c8 100644 --- a/drivers/misc/mic/host/mic_intr.c +++ b/drivers/misc/mic/host/mic_intr.c @@ -363,8 +363,6 @@ static int mic_setup_intx(struct mic_device *mdev, struct pci_dev *pdev) { int rc; - pci_msi_off(pdev); - /* Enable intx */ pci_intx(pdev, 1); rc = mic_setup_callbacks(mdev); -- cgit v0.10.2 From 127af8828518074ab24e9b5678229513d198d832 Mon Sep 17 00:00:00 2001 From: Eli Billauer Date: Fri, 27 Mar 2015 11:56:06 +0300 Subject: char: xillybus: Don't return -EFAULT on user-triggered flush The API allows the application to flush a host-to-FPGA stream by calling write() with the data count set to zero. Before this patch, copy_from_user() was called with a non-zero byte count, which possibly made it attempt to read from unmapped user memory. Such attempts caused the driver to return -EFAULT instead of 0, even though the desired operation went through fine. This patch ensures the driver returns 0 on a successful flush. Signed-off-by: Eli Billauer Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/char/xillybus/xillybus_core.c b/drivers/char/xillybus/xillybus_core.c index b827fa0..77d6c12 100644 --- a/drivers/char/xillybus/xillybus_core.c +++ b/drivers/char/xillybus/xillybus_core.c @@ -1237,6 +1237,8 @@ static ssize_t xillybus_write(struct file *filp, const char __user *userbuf, unsigned char *tail; int i; + howmany = 0; + end_offset_plus1 = bufpos >> channel->log2_element_size; -- cgit v0.10.2 From d86fb45b5c52d8f2a3e78e81afd85a2a9d4478d7 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Thu, 26 Mar 2015 22:12:49 +0300 Subject: mcb: request_mem_region() returns NULL on error The code here is checking for IS_ERR() when request_mem_region() only returns NULL on error and never an ERR_PTR. Signed-off-by: Dan Carpenter Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/mcb/mcb-pci.c b/drivers/mcb/mcb-pci.c index 0af7361..de36237 100644 --- a/drivers/mcb/mcb-pci.c +++ b/drivers/mcb/mcb-pci.c @@ -56,9 +56,9 @@ static int mcb_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) res = request_mem_region(priv->mapbase, CHAM_HEADER_SIZE, KBUILD_MODNAME); - if (IS_ERR(res)) { + if (!res) { dev_err(&pdev->dev, "Failed to request PCI memory\n"); - ret = PTR_ERR(res); + ret = -EBUSY; goto out_disable; } -- cgit v0.10.2 From 8c02a5ba34a1fae6def8cb5a39bb582f09bca49c Mon Sep 17 00:00:00 2001 From: Mathieu Poirier Date: Mon, 30 Mar 2015 14:13:34 -0600 Subject: coresight: making cpu index lookup arm64 compliant Function "get_logical_index()" is not available on arm64. Instead of adding the function simply using "of_get_cpu_node()" and comparing the return value with cpu handles yields the same result. Signed-off-by: Mathieu Poirier Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/coresight/of_coresight.c b/drivers/coresight/of_coresight.c index c3efa41..f3cc8e9 100644 --- a/drivers/coresight/of_coresight.c +++ b/drivers/coresight/of_coresight.c @@ -22,6 +22,7 @@ #include #include #include +#include #include @@ -113,7 +114,7 @@ static int of_coresight_alloc_memory(struct device *dev, struct coresight_platform_data *of_get_coresight_platform_data( struct device *dev, struct device_node *node) { - int i = 0, ret = 0; + int i = 0, ret = 0, cpu; struct coresight_platform_data *pdata; struct of_endpoint endpoint, rendpoint; struct device *rdev; @@ -187,17 +188,10 @@ struct coresight_platform_data *of_get_coresight_platform_data( /* Affinity defaults to CPU0 */ pdata->cpu = 0; dn = of_parse_phandle(node, "cpu", 0); - if (dn) { - const u32 *cell; - int len, index; - u64 hwid; - - cell = of_get_property(dn, "reg", &len); - if (cell) { - hwid = of_read_number(cell, of_n_addr_cells(dn)); - index = get_logical_index(hwid); - if (index != -EINVAL) - pdata->cpu = index; + for (cpu = 0; dn && cpu < nr_cpu_ids; cpu++) { + if (dn == of_get_cpu_node(cpu, NULL)) { + pdata->cpu = cpu; + break; } } -- cgit v0.10.2 From 72f641fe6818a403aed52fb3a5b8a241ff76c24f Mon Sep 17 00:00:00 2001 From: Mathieu Poirier Date: Mon, 30 Mar 2015 14:13:35 -0600 Subject: coresight: fixing compilation warnings picked up by 64bit compiler Compiling coresight drivers with a 64-bit compiler highlights a couple of formatting issues, which are fixed by this patch. Signed-off-by: Mathieu Poirier Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/coresight/coresight-etb10.c b/drivers/coresight/coresight-etb10.c index c9acd40..4004986 100644 --- a/drivers/coresight/coresight-etb10.c +++ b/drivers/coresight/coresight-etb10.c @@ -313,8 +313,8 @@ static ssize_t etb_read(struct file *file, char __user *data, *ppos += len; - dev_dbg(drvdata->dev, "%s: %d bytes copied, %d bytes left\n", - __func__, len, (int) (depth * 4 - *ppos)); + dev_dbg(drvdata->dev, "%s: %zu bytes copied, %d bytes left\n", + __func__, len, (int)(depth * 4 - *ppos)); return len; } diff --git a/drivers/coresight/coresight-tmc.c b/drivers/coresight/coresight-tmc.c index 3ff232f..030e097 100644 --- a/drivers/coresight/coresight-tmc.c +++ b/drivers/coresight/coresight-tmc.c @@ -533,8 +533,8 @@ static ssize_t tmc_read(struct file *file, char __user *data, size_t len, *ppos += len; - dev_dbg(drvdata->dev, "%s: %d bytes copied, %d bytes left\n", - __func__, len, (int) (drvdata->size - *ppos)); + dev_dbg(drvdata->dev, "%s: %zu bytes copied, %d bytes left\n", + __func__, len, (int)(drvdata->size - *ppos)); return len; } -- cgit v0.10.2 From 3288731e628e0269c20d86e43b647d0b92f2b3fc Mon Sep 17 00:00:00 2001 From: Mathieu Poirier Date: Mon, 30 Mar 2015 14:13:36 -0600 Subject: coresight: Adding coresight support for arm64 architecture Most CoreSight blocks are 64-bit ready. As such move configuration entries from "arch/arm/Kconfig.config" to the driver's subdirectory and source the newly created Kconfig from architecture specific Kconfig.debug files. Signed-off-by: Mathieu Poirier Acked-by: Catalin Marinas Signed-off-by: Greg Kroah-Hartman diff --git a/arch/arm/Kconfig.debug b/arch/arm/Kconfig.debug index 970de75..8d14ad4 100644 --- a/arch/arm/Kconfig.debug +++ b/arch/arm/Kconfig.debug @@ -1610,59 +1610,6 @@ config DEBUG_SET_MODULE_RONX against certain classes of kernel exploits. If in doubt, say "N". -menuconfig CORESIGHT - bool "CoreSight Tracing Support" - select ARM_AMBA - help - This framework provides a kernel interface for the CoreSight debug - and trace drivers to register themselves with. It's intended to build - a topological view of the CoreSight components based on a DT - specification and configure the right serie of components when a - trace source gets enabled. - -if CORESIGHT -config CORESIGHT_LINKS_AND_SINKS - bool "CoreSight Link and Sink drivers" - help - This enables support for CoreSight link and sink drivers that are - responsible for transporting and collecting the trace data - respectively. Link and sinks are dynamically aggregated with a trace - entity at run time to form a complete trace path. - -config CORESIGHT_LINK_AND_SINK_TMC - bool "Coresight generic TMC driver" - depends on CORESIGHT_LINKS_AND_SINKS - help - This enables support for the Trace Memory Controller driver. Depending - on its configuration the device can act as a link (embedded trace router - - ETR) or sink (embedded trace FIFO). The driver complies with the - generic implementation of the component without special enhancement or - added features. - -config CORESIGHT_SINK_TPIU - bool "Coresight generic TPIU driver" - depends on CORESIGHT_LINKS_AND_SINKS - help - This enables support for the Trace Port Interface Unit driver, responsible - for bridging the gap between the on-chip coresight components and a trace - port collection engine, typically connected to an external host for use - case capturing more traces than the on-board coresight memory can handle. - -config CORESIGHT_SINK_ETBV10 - bool "Coresight ETBv1.0 driver" - depends on CORESIGHT_LINKS_AND_SINKS - help - This enables support for the Embedded Trace Buffer version 1.0 driver - that complies with the generic implementation of the component without - special enhancement or added features. +source "drivers/coresight/Kconfig" -config CORESIGHT_SOURCE_ETM3X - bool "CoreSight Embedded Trace Macrocell 3.x driver" - select CORESIGHT_LINKS_AND_SINKS - help - This driver provides support for processor ETM3.x and PTM1.x modules, - which allows tracing the instructions that a processor is executing - This is primarily useful for instruction level tracing. Depending - the ETM version data tracing may also be available. -endif endmenu diff --git a/arch/arm64/Kconfig.debug b/arch/arm64/Kconfig.debug index 4a87410..5b2ffd8 100644 --- a/arch/arm64/Kconfig.debug +++ b/arch/arm64/Kconfig.debug @@ -89,4 +89,6 @@ config DEBUG_ALIGN_RODATA If in doubt, say N +source "drivers/coresight/Kconfig" + endmenu diff --git a/drivers/coresight/Kconfig b/drivers/coresight/Kconfig new file mode 100644 index 0000000..fc1f1ae --- /dev/null +++ b/drivers/coresight/Kconfig @@ -0,0 +1,61 @@ +# +# Coresight configuration +# +menuconfig CORESIGHT + bool "CoreSight Tracing Support" + select ARM_AMBA + help + This framework provides a kernel interface for the CoreSight debug + and trace drivers to register themselves with. It's intended to build + a topological view of the CoreSight components based on a DT + specification and configure the right serie of components when a + trace source gets enabled. + +if CORESIGHT +config CORESIGHT_LINKS_AND_SINKS + bool "CoreSight Link and Sink drivers" + help + This enables support for CoreSight link and sink drivers that are + responsible for transporting and collecting the trace data + respectively. Link and sinks are dynamically aggregated with a trace + entity at run time to form a complete trace path. + +config CORESIGHT_LINK_AND_SINK_TMC + bool "Coresight generic TMC driver" + depends on CORESIGHT_LINKS_AND_SINKS + help + This enables support for the Trace Memory Controller driver. + Depending on its configuration the device can act as a link (embedded + trace router - ETR) or sink (embedded trace FIFO). The driver + complies with the generic implementation of the component without + special enhancement or added features. + +config CORESIGHT_SINK_TPIU + bool "Coresight generic TPIU driver" + depends on CORESIGHT_LINKS_AND_SINKS + help + This enables support for the Trace Port Interface Unit driver, + responsible for bridging the gap between the on-chip coresight + components and a trace for bridging the gap between the on-chip + coresight components and a trace port collection engine, typically + connected to an external host for use case capturing more traces than + the on-board coresight memory can handle. + +config CORESIGHT_SINK_ETBV10 + bool "Coresight ETBv1.0 driver" + depends on CORESIGHT_LINKS_AND_SINKS + help + This enables support for the Embedded Trace Buffer version 1.0 driver + that complies with the generic implementation of the component without + special enhancement or added features. + +config CORESIGHT_SOURCE_ETM3X + bool "CoreSight Embedded Trace Macrocell 3.x driver" + depends on !ARM64 + select CORESIGHT_LINKS_AND_SINKS + help + This driver provides support for processor ETM3.x and PTM1.x modules, + which allows tracing the instructions that a processor is executing + This is primarily useful for instruction level tracing. Depending + the ETM version data tracing may also be available. +endif -- cgit v0.10.2 From b29d5c1f057f583bc15be3d1ec4993e90cc53641 Mon Sep 17 00:00:00 2001 From: Mathieu Poirier Date: Mon, 30 Mar 2015 14:13:37 -0600 Subject: coresight: Correcting documentation typographical error Signed-off-by: Mathieu Poirier Signed-off-by: Greg Kroah-Hartman diff --git a/Documentation/trace/coresight.txt b/Documentation/trace/coresight.txt index 0236155..77d14d5 100644 --- a/Documentation/trace/coresight.txt +++ b/Documentation/trace/coresight.txt @@ -14,7 +14,7 @@ document is concerned with the latter. HW assisted tracing is becoming increasingly useful when dealing with systems that have many SoCs and other components like GPU and DMA engines. ARM has developed a HW assisted tracing solution by means of different components, each -being added to a design at systhesis time to cater to specific tracing needs. +being added to a design at synthesis time to cater to specific tracing needs. Compoments are generally categorised as source, link and sinks and are (usually) discovered using the AMBA bus. -- cgit v0.10.2 From a0a500efab41c761a1d1fd919d2e42a3a3545b2e Mon Sep 17 00:00:00 2001 From: Xia Kaixu Date: Mon, 30 Mar 2015 14:13:38 -0600 Subject: coresight: adding the LINKSINK block as a sink type >From the TMC TRM, the ETF can be configured as buffer mode, so ETF can be a sink type. Signed-off-by: Xia Kaixu Signed-off-by: Mathieu Poirier Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/coresight/coresight.c b/drivers/coresight/coresight.c index c5def93..894531d 100644 --- a/drivers/coresight/coresight.c +++ b/drivers/coresight/coresight.c @@ -305,7 +305,9 @@ static int coresight_build_paths(struct coresight_device *csdev, list_add(&csdev->path_link, path); - if (csdev->type == CORESIGHT_DEV_TYPE_SINK && csdev->activated) { + if ((csdev->type == CORESIGHT_DEV_TYPE_SINK || + csdev->type == CORESIGHT_DEV_TYPE_LINKSINK) && + csdev->activated) { if (enable) ret = coresight_enable_path(path); else -- cgit v0.10.2 From 223437c72ae008094e43199dea93c3a144c4e153 Mon Sep 17 00:00:00 2001 From: Kaixu Xia Date: Mon, 30 Mar 2015 14:13:39 -0600 Subject: coresight: remove the unnecessary configuration coresight-default-sink The coresight-default-sink configuration option has been removed from the framework. As such remove it from DT and bindings. Signed-off-by: Kaixu Xia Signed-off-by: Mathieu Poirier Signed-off-by: Greg Kroah-Hartman diff --git a/Documentation/devicetree/bindings/arm/coresight.txt b/Documentation/devicetree/bindings/arm/coresight.txt index a308935..88602b7 100644 --- a/Documentation/devicetree/bindings/arm/coresight.txt +++ b/Documentation/devicetree/bindings/arm/coresight.txt @@ -61,7 +61,6 @@ Example: compatible = "arm,coresight-etb10", "arm,primecell"; reg = <0 0x20010000 0 0x1000>; - coresight-default-sink; clocks = <&oscclk6a>; clock-names = "apb_pclk"; port { diff --git a/arch/arm/boot/dts/hip04.dtsi b/arch/arm/boot/dts/hip04.dtsi index 2388145..44044f2 100644 --- a/arch/arm/boot/dts/hip04.dtsi +++ b/arch/arm/boot/dts/hip04.dtsi @@ -275,7 +275,6 @@ compatible = "arm,coresight-etb10", "arm,primecell"; reg = <0 0xe3c42000 0 0x1000>; - coresight-default-sink; clocks = <&clk_375m>; clock-names = "apb_pclk"; port { diff --git a/arch/arm/boot/dts/omap3-beagle-xm.dts b/arch/arm/boot/dts/omap3-beagle-xm.dts index 25f7b0a..8cdca51 100644 --- a/arch/arm/boot/dts/omap3-beagle-xm.dts +++ b/arch/arm/boot/dts/omap3-beagle-xm.dts @@ -150,7 +150,6 @@ compatible = "arm,coresight-etb10", "arm,primecell"; reg = <0x5401b000 0x1000>; - coresight-default-sink; clocks = <&emu_src_ck>; clock-names = "apb_pclk"; port { diff --git a/arch/arm/boot/dts/omap3-beagle.dts b/arch/arm/boot/dts/omap3-beagle.dts index c792391..6d4c46b 100644 --- a/arch/arm/boot/dts/omap3-beagle.dts +++ b/arch/arm/boot/dts/omap3-beagle.dts @@ -145,7 +145,6 @@ compatible = "arm,coresight-etb10", "arm,primecell"; reg = <0x5401b000 0x1000>; - coresight-default-sink; clocks = <&emu_src_ck>; clock-names = "apb_pclk"; port { diff --git a/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts b/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts index 33920df..7a2aeac 100644 --- a/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts +++ b/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts @@ -362,7 +362,6 @@ compatible = "arm,coresight-etb10", "arm,primecell"; reg = <0 0x20010000 0 0x1000>; - coresight-default-sink; clocks = <&oscclk6a>; clock-names = "apb_pclk"; port { -- cgit v0.10.2 From a2d6e1849329b7735f2872af4221727c7b9502dd Mon Sep 17 00:00:00 2001 From: Mathieu Poirier Date: Mon, 30 Mar 2015 14:13:40 -0600 Subject: coresight-tmc: Adding a status interface to sysfs Knowing the state of various control register is always useful for degging and tuning. As such add an entry in sysfs that expose to userspace the most important registers. Signed-off-by: Mathieu Poirier Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/coresight/coresight-tmc.c b/drivers/coresight/coresight-tmc.c index 030e097..7147f3d 100644 --- a/drivers/coresight/coresight-tmc.c +++ b/drivers/coresight/coresight-tmc.c @@ -565,6 +565,59 @@ static const struct file_operations tmc_fops = { .llseek = no_llseek, }; +static ssize_t status_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret; + unsigned long flags; + u32 tmc_rsz, tmc_sts, tmc_rrp, tmc_rwp, tmc_trg; + u32 tmc_ctl, tmc_ffsr, tmc_ffcr, tmc_mode, tmc_pscr; + u32 devid; + struct tmc_drvdata *drvdata = dev_get_drvdata(dev->parent); + + ret = clk_prepare_enable(drvdata->clk); + if (ret) + goto out; + + spin_lock_irqsave(&drvdata->spinlock, flags); + CS_UNLOCK(drvdata->base); + + tmc_rsz = readl_relaxed(drvdata->base + TMC_RSZ); + tmc_sts = readl_relaxed(drvdata->base + TMC_STS); + tmc_rrp = readl_relaxed(drvdata->base + TMC_RRP); + tmc_rwp = readl_relaxed(drvdata->base + TMC_RWP); + tmc_trg = readl_relaxed(drvdata->base + TMC_TRG); + tmc_ctl = readl_relaxed(drvdata->base + TMC_CTL); + tmc_ffsr = readl_relaxed(drvdata->base + TMC_FFSR); + tmc_ffcr = readl_relaxed(drvdata->base + TMC_FFCR); + tmc_mode = readl_relaxed(drvdata->base + TMC_MODE); + tmc_pscr = readl_relaxed(drvdata->base + TMC_PSCR); + devid = readl_relaxed(drvdata->base + CORESIGHT_DEVID); + + CS_LOCK(drvdata->base); + spin_unlock_irqrestore(&drvdata->spinlock, flags); + + clk_disable_unprepare(drvdata->clk); + + return sprintf(buf, + "Depth:\t\t0x%x\n" + "Status:\t\t0x%x\n" + "RAM read ptr:\t0x%x\n" + "RAM wrt ptr:\t0x%x\n" + "Trigger cnt:\t0x%x\n" + "Control:\t0x%x\n" + "Flush status:\t0x%x\n" + "Flush ctrl:\t0x%x\n" + "Mode:\t\t0x%x\n" + "PSRC:\t\t0x%x\n" + "DEVID:\t\t0x%x\n", + tmc_rsz, tmc_sts, tmc_rrp, tmc_rwp, tmc_trg, + tmc_ctl, tmc_ffsr, tmc_ffcr, tmc_mode, tmc_pscr, devid); +out: + return -EINVAL; +} +static DEVICE_ATTR_RO(status); + static ssize_t trigger_cntr_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -593,18 +646,21 @@ static DEVICE_ATTR_RW(trigger_cntr); static struct attribute *coresight_etb_attrs[] = { &dev_attr_trigger_cntr.attr, + &dev_attr_status.attr, NULL, }; ATTRIBUTE_GROUPS(coresight_etb); static struct attribute *coresight_etr_attrs[] = { &dev_attr_trigger_cntr.attr, + &dev_attr_status.attr, NULL, }; ATTRIBUTE_GROUPS(coresight_etr); static struct attribute *coresight_etf_attrs[] = { &dev_attr_trigger_cntr.attr, + &dev_attr_status.attr, NULL, }; ATTRIBUTE_GROUPS(coresight_etf); -- cgit v0.10.2 From 01081f5ab9916603555f236b11f76bb00e4e01e9 Mon Sep 17 00:00:00 2001 From: Mathieu Poirier Date: Mon, 30 Mar 2015 14:13:41 -0600 Subject: coresight: moving to new "hwtracing" directory Keeping drivers related to HW tracing on ARM, i.e coresight, under "drivers/coresight" doesn't make sense when other architectures start rolling out technologies of the same nature. As such creating a new "drivers/hwtracing" directory where all drivers of the same kind can reside, reducing namespace pollution under "drivers/". Signed-off-by: Mathieu Poirier Signed-off-by: Greg Kroah-Hartman diff --git a/MAINTAINERS b/MAINTAINERS index d749002..e9e12d7 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -953,7 +953,7 @@ ARM/CORESIGHT FRAMEWORK AND DRIVERS M: Mathieu Poirier L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers) S: Maintained -F: drivers/coresight/* +F: drivers/hwtracing/coresight/* F: Documentation/trace/coresight.txt F: Documentation/devicetree/bindings/arm/coresight.txt F: Documentation/ABI/testing/sysfs-bus-coresight-devices-* diff --git a/arch/arm/Kconfig.debug b/arch/arm/Kconfig.debug index 8d14ad4..8b0183a 100644 --- a/arch/arm/Kconfig.debug +++ b/arch/arm/Kconfig.debug @@ -1610,6 +1610,6 @@ config DEBUG_SET_MODULE_RONX against certain classes of kernel exploits. If in doubt, say "N". -source "drivers/coresight/Kconfig" +source "drivers/hwtracing/coresight/Kconfig" endmenu diff --git a/arch/arm64/Kconfig.debug b/arch/arm64/Kconfig.debug index 5b2ffd8..d6285ef 100644 --- a/arch/arm64/Kconfig.debug +++ b/arch/arm64/Kconfig.debug @@ -89,6 +89,6 @@ config DEBUG_ALIGN_RODATA If in doubt, say N -source "drivers/coresight/Kconfig" +source "drivers/hwtracing/coresight/Kconfig" endmenu diff --git a/drivers/Makefile b/drivers/Makefile index 527a6da..46d2554 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -163,5 +163,5 @@ obj-$(CONFIG_POWERCAP) += powercap/ obj-$(CONFIG_MCB) += mcb/ obj-$(CONFIG_RAS) += ras/ obj-$(CONFIG_THUNDERBOLT) += thunderbolt/ -obj-$(CONFIG_CORESIGHT) += coresight/ +obj-$(CONFIG_CORESIGHT) += hwtracing/coresight/ obj-$(CONFIG_ANDROID) += android/ diff --git a/drivers/coresight/Kconfig b/drivers/coresight/Kconfig deleted file mode 100644 index fc1f1ae..0000000 --- a/drivers/coresight/Kconfig +++ /dev/null @@ -1,61 +0,0 @@ -# -# Coresight configuration -# -menuconfig CORESIGHT - bool "CoreSight Tracing Support" - select ARM_AMBA - help - This framework provides a kernel interface for the CoreSight debug - and trace drivers to register themselves with. It's intended to build - a topological view of the CoreSight components based on a DT - specification and configure the right serie of components when a - trace source gets enabled. - -if CORESIGHT -config CORESIGHT_LINKS_AND_SINKS - bool "CoreSight Link and Sink drivers" - help - This enables support for CoreSight link and sink drivers that are - responsible for transporting and collecting the trace data - respectively. Link and sinks are dynamically aggregated with a trace - entity at run time to form a complete trace path. - -config CORESIGHT_LINK_AND_SINK_TMC - bool "Coresight generic TMC driver" - depends on CORESIGHT_LINKS_AND_SINKS - help - This enables support for the Trace Memory Controller driver. - Depending on its configuration the device can act as a link (embedded - trace router - ETR) or sink (embedded trace FIFO). The driver - complies with the generic implementation of the component without - special enhancement or added features. - -config CORESIGHT_SINK_TPIU - bool "Coresight generic TPIU driver" - depends on CORESIGHT_LINKS_AND_SINKS - help - This enables support for the Trace Port Interface Unit driver, - responsible for bridging the gap between the on-chip coresight - components and a trace for bridging the gap between the on-chip - coresight components and a trace port collection engine, typically - connected to an external host for use case capturing more traces than - the on-board coresight memory can handle. - -config CORESIGHT_SINK_ETBV10 - bool "Coresight ETBv1.0 driver" - depends on CORESIGHT_LINKS_AND_SINKS - help - This enables support for the Embedded Trace Buffer version 1.0 driver - that complies with the generic implementation of the component without - special enhancement or added features. - -config CORESIGHT_SOURCE_ETM3X - bool "CoreSight Embedded Trace Macrocell 3.x driver" - depends on !ARM64 - select CORESIGHT_LINKS_AND_SINKS - help - This driver provides support for processor ETM3.x and PTM1.x modules, - which allows tracing the instructions that a processor is executing - This is primarily useful for instruction level tracing. Depending - the ETM version data tracing may also be available. -endif diff --git a/drivers/coresight/Makefile b/drivers/coresight/Makefile deleted file mode 100644 index 4b4bec8..0000000 --- a/drivers/coresight/Makefile +++ /dev/null @@ -1,11 +0,0 @@ -# -# Makefile for CoreSight drivers. -# -obj-$(CONFIG_CORESIGHT) += coresight.o -obj-$(CONFIG_OF) += of_coresight.o -obj-$(CONFIG_CORESIGHT_LINK_AND_SINK_TMC) += coresight-tmc.o -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 diff --git a/drivers/coresight/coresight-etb10.c b/drivers/coresight/coresight-etb10.c deleted file mode 100644 index 4004986..0000000 --- a/drivers/coresight/coresight-etb10.c +++ /dev/null @@ -1,527 +0,0 @@ -/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 and - * only version 2 as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "coresight-priv.h" - -#define ETB_RAM_DEPTH_REG 0x004 -#define ETB_STATUS_REG 0x00c -#define ETB_RAM_READ_DATA_REG 0x010 -#define ETB_RAM_READ_POINTER 0x014 -#define ETB_RAM_WRITE_POINTER 0x018 -#define ETB_TRG 0x01c -#define ETB_CTL_REG 0x020 -#define ETB_RWD_REG 0x024 -#define ETB_FFSR 0x300 -#define ETB_FFCR 0x304 -#define ETB_ITMISCOP0 0xee0 -#define ETB_ITTRFLINACK 0xee4 -#define ETB_ITTRFLIN 0xee8 -#define ETB_ITATBDATA0 0xeeC -#define ETB_ITATBCTR2 0xef0 -#define ETB_ITATBCTR1 0xef4 -#define ETB_ITATBCTR0 0xef8 - -/* register description */ -/* STS - 0x00C */ -#define ETB_STATUS_RAM_FULL BIT(0) -/* CTL - 0x020 */ -#define ETB_CTL_CAPT_EN BIT(0) -/* FFCR - 0x304 */ -#define ETB_FFCR_EN_FTC BIT(0) -#define ETB_FFCR_FON_MAN BIT(6) -#define ETB_FFCR_STOP_FI BIT(12) -#define ETB_FFCR_STOP_TRIGGER BIT(13) - -#define ETB_FFCR_BIT 6 -#define ETB_FFSR_BIT 1 -#define ETB_FRAME_SIZE_WORDS 4 - -/** - * struct etb_drvdata - specifics associated to an ETB component - * @base: memory mapped base address for this component. - * @dev: the device entity associated to this component. - * @csdev: component vitals needed by the framework. - * @miscdev: specifics to handle "/dev/xyz.etb" entry. - * @clk: the clock this component is associated to. - * @spinlock: only one at a time pls. - * @in_use: synchronise user space access to etb buffer. - * @buf: area of memory where ETB buffer content gets sent. - * @buffer_depth: size of @buf. - * @enable: this ETB is being used. - * @trigger_cntr: amount of words to store after a trigger. - */ -struct etb_drvdata { - void __iomem *base; - struct device *dev; - struct coresight_device *csdev; - struct miscdevice miscdev; - struct clk *clk; - spinlock_t spinlock; - atomic_t in_use; - u8 *buf; - u32 buffer_depth; - bool enable; - u32 trigger_cntr; -}; - -static unsigned int etb_get_buffer_depth(struct etb_drvdata *drvdata) -{ - int ret; - u32 depth = 0; - - ret = clk_prepare_enable(drvdata->clk); - if (ret) - return ret; - - /* RO registers don't need locking */ - depth = readl_relaxed(drvdata->base + ETB_RAM_DEPTH_REG); - - clk_disable_unprepare(drvdata->clk); - return depth; -} - -static void etb_enable_hw(struct etb_drvdata *drvdata) -{ - int i; - u32 depth; - - CS_UNLOCK(drvdata->base); - - depth = drvdata->buffer_depth; - /* reset write RAM pointer address */ - writel_relaxed(0x0, drvdata->base + ETB_RAM_WRITE_POINTER); - /* clear entire RAM buffer */ - for (i = 0; i < depth; i++) - writel_relaxed(0x0, drvdata->base + ETB_RWD_REG); - - /* reset write RAM pointer address */ - writel_relaxed(0x0, drvdata->base + ETB_RAM_WRITE_POINTER); - /* reset read RAM pointer address */ - writel_relaxed(0x0, drvdata->base + ETB_RAM_READ_POINTER); - - writel_relaxed(drvdata->trigger_cntr, drvdata->base + ETB_TRG); - writel_relaxed(ETB_FFCR_EN_FTC | ETB_FFCR_STOP_TRIGGER, - drvdata->base + ETB_FFCR); - /* ETB trace capture enable */ - writel_relaxed(ETB_CTL_CAPT_EN, drvdata->base + ETB_CTL_REG); - - CS_LOCK(drvdata->base); -} - -static int etb_enable(struct coresight_device *csdev) -{ - struct etb_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); - int ret; - unsigned long flags; - - ret = clk_prepare_enable(drvdata->clk); - if (ret) - return ret; - - spin_lock_irqsave(&drvdata->spinlock, flags); - etb_enable_hw(drvdata); - drvdata->enable = true; - spin_unlock_irqrestore(&drvdata->spinlock, flags); - - dev_info(drvdata->dev, "ETB enabled\n"); - return 0; -} - -static void etb_disable_hw(struct etb_drvdata *drvdata) -{ - u32 ffcr; - - CS_UNLOCK(drvdata->base); - - ffcr = readl_relaxed(drvdata->base + ETB_FFCR); - /* stop formatter when a stop has completed */ - ffcr |= ETB_FFCR_STOP_FI; - writel_relaxed(ffcr, drvdata->base + ETB_FFCR); - /* manually generate a flush of the system */ - ffcr |= ETB_FFCR_FON_MAN; - writel_relaxed(ffcr, drvdata->base + ETB_FFCR); - - if (coresight_timeout(drvdata->base, ETB_FFCR, ETB_FFCR_BIT, 0)) { - dev_err(drvdata->dev, - "timeout observed when probing at offset %#x\n", - ETB_FFCR); - } - - /* disable trace capture */ - writel_relaxed(0x0, drvdata->base + ETB_CTL_REG); - - if (coresight_timeout(drvdata->base, ETB_FFSR, ETB_FFSR_BIT, 1)) { - dev_err(drvdata->dev, - "timeout observed when probing at offset %#x\n", - ETB_FFCR); - } - - CS_LOCK(drvdata->base); -} - -static void etb_dump_hw(struct etb_drvdata *drvdata) -{ - int i; - u8 *buf_ptr; - u32 read_data, depth; - u32 read_ptr, write_ptr; - u32 frame_off, frame_endoff; - - CS_UNLOCK(drvdata->base); - - read_ptr = readl_relaxed(drvdata->base + ETB_RAM_READ_POINTER); - write_ptr = readl_relaxed(drvdata->base + ETB_RAM_WRITE_POINTER); - - frame_off = write_ptr % ETB_FRAME_SIZE_WORDS; - frame_endoff = ETB_FRAME_SIZE_WORDS - frame_off; - if (frame_off) { - dev_err(drvdata->dev, - "write_ptr: %lu not aligned to formatter frame size\n", - (unsigned long)write_ptr); - dev_err(drvdata->dev, "frameoff: %lu, frame_endoff: %lu\n", - (unsigned long)frame_off, (unsigned long)frame_endoff); - write_ptr += frame_endoff; - } - - if ((readl_relaxed(drvdata->base + ETB_STATUS_REG) - & ETB_STATUS_RAM_FULL) == 0) - writel_relaxed(0x0, drvdata->base + ETB_RAM_READ_POINTER); - else - writel_relaxed(write_ptr, drvdata->base + ETB_RAM_READ_POINTER); - - depth = drvdata->buffer_depth; - buf_ptr = drvdata->buf; - for (i = 0; i < depth; i++) { - 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; - } - - if (frame_off) { - buf_ptr -= (frame_endoff * 4); - for (i = 0; i < frame_endoff; i++) { - *buf_ptr++ = 0x0; - *buf_ptr++ = 0x0; - *buf_ptr++ = 0x0; - *buf_ptr++ = 0x0; - } - } - - writel_relaxed(read_ptr, drvdata->base + ETB_RAM_READ_POINTER); - - CS_LOCK(drvdata->base); -} - -static void etb_disable(struct coresight_device *csdev) -{ - struct etb_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); - unsigned long flags; - - spin_lock_irqsave(&drvdata->spinlock, flags); - etb_disable_hw(drvdata); - etb_dump_hw(drvdata); - drvdata->enable = false; - spin_unlock_irqrestore(&drvdata->spinlock, flags); - - clk_disable_unprepare(drvdata->clk); - - dev_info(drvdata->dev, "ETB disabled\n"); -} - -static const struct coresight_ops_sink etb_sink_ops = { - .enable = etb_enable, - .disable = etb_disable, -}; - -static const struct coresight_ops etb_cs_ops = { - .sink_ops = &etb_sink_ops, -}; - -static void etb_dump(struct etb_drvdata *drvdata) -{ - unsigned long flags; - - spin_lock_irqsave(&drvdata->spinlock, flags); - if (drvdata->enable) { - etb_disable_hw(drvdata); - etb_dump_hw(drvdata); - etb_enable_hw(drvdata); - } - spin_unlock_irqrestore(&drvdata->spinlock, flags); - - dev_info(drvdata->dev, "ETB dumped\n"); -} - -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)) - return -EBUSY; - - dev_dbg(drvdata->dev, "%s: successfully opened\n", __func__); - return 0; -} - -static ssize_t etb_read(struct file *file, char __user *data, - size_t len, loff_t *ppos) -{ - u32 depth; - struct etb_drvdata *drvdata = container_of(file->private_data, - struct etb_drvdata, miscdev); - - etb_dump(drvdata); - - depth = drvdata->buffer_depth; - if (*ppos + len > depth * 4) - len = depth * 4 - *ppos; - - if (copy_to_user(data, drvdata->buf + *ppos, len)) { - dev_dbg(drvdata->dev, "%s: copy_to_user failed\n", __func__); - return -EFAULT; - } - - *ppos += len; - - dev_dbg(drvdata->dev, "%s: %zu bytes copied, %d bytes left\n", - __func__, len, (int)(depth * 4 - *ppos)); - return len; -} - -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); - - dev_dbg(drvdata->dev, "%s: released\n", __func__); - return 0; -} - -static const struct file_operations etb_fops = { - .owner = THIS_MODULE, - .open = etb_open, - .read = etb_read, - .release = etb_release, - .llseek = no_llseek, -}; - -static ssize_t status_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - int ret; - unsigned long flags; - u32 etb_rdr, etb_sr, etb_rrp, etb_rwp; - u32 etb_trg, etb_cr, etb_ffsr, etb_ffcr; - struct etb_drvdata *drvdata = dev_get_drvdata(dev->parent); - - ret = clk_prepare_enable(drvdata->clk); - if (ret) - goto out; - - spin_lock_irqsave(&drvdata->spinlock, flags); - CS_UNLOCK(drvdata->base); - - etb_rdr = readl_relaxed(drvdata->base + ETB_RAM_DEPTH_REG); - etb_sr = readl_relaxed(drvdata->base + ETB_STATUS_REG); - etb_rrp = readl_relaxed(drvdata->base + ETB_RAM_READ_POINTER); - etb_rwp = readl_relaxed(drvdata->base + ETB_RAM_WRITE_POINTER); - etb_trg = readl_relaxed(drvdata->base + ETB_TRG); - etb_cr = readl_relaxed(drvdata->base + ETB_CTL_REG); - etb_ffsr = readl_relaxed(drvdata->base + ETB_FFSR); - etb_ffcr = readl_relaxed(drvdata->base + ETB_FFCR); - - CS_LOCK(drvdata->base); - spin_unlock_irqrestore(&drvdata->spinlock, flags); - - clk_disable_unprepare(drvdata->clk); - - return sprintf(buf, - "Depth:\t\t0x%x\n" - "Status:\t\t0x%x\n" - "RAM read ptr:\t0x%x\n" - "RAM wrt ptr:\t0x%x\n" - "Trigger cnt:\t0x%x\n" - "Control:\t0x%x\n" - "Flush status:\t0x%x\n" - "Flush ctrl:\t0x%x\n", - etb_rdr, etb_sr, etb_rrp, etb_rwp, - etb_trg, etb_cr, etb_ffsr, etb_ffcr); -out: - return -EINVAL; -} -static DEVICE_ATTR_RO(status); - -static ssize_t trigger_cntr_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct etb_drvdata *drvdata = dev_get_drvdata(dev->parent); - unsigned long val = drvdata->trigger_cntr; - - return sprintf(buf, "%#lx\n", val); -} - -static ssize_t trigger_cntr_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t size) -{ - int ret; - unsigned long val; - struct etb_drvdata *drvdata = dev_get_drvdata(dev->parent); - - ret = kstrtoul(buf, 16, &val); - if (ret) - return ret; - - drvdata->trigger_cntr = val; - return size; -} -static DEVICE_ATTR_RW(trigger_cntr); - -static struct attribute *coresight_etb_attrs[] = { - &dev_attr_trigger_cntr.attr, - &dev_attr_status.attr, - NULL, -}; -ATTRIBUTE_GROUPS(coresight_etb); - -static int etb_probe(struct amba_device *adev, const struct amba_id *id) -{ - int ret; - void __iomem *base; - struct device *dev = &adev->dev; - struct coresight_platform_data *pdata = NULL; - struct etb_drvdata *drvdata; - struct resource *res = &adev->res; - struct coresight_desc *desc; - struct device_node *np = adev->dev.of_node; - - if (np) { - pdata = of_get_coresight_platform_data(dev, np); - if (IS_ERR(pdata)) - return PTR_ERR(pdata); - adev->dev.platform_data = pdata; - } - - drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); - if (!drvdata) - return -ENOMEM; - - drvdata->dev = &adev->dev; - dev_set_drvdata(dev, drvdata); - - /* validity for the resource is already checked by the AMBA core */ - base = devm_ioremap_resource(dev, res); - if (IS_ERR(base)) - return PTR_ERR(base); - - drvdata->base = base; - - spin_lock_init(&drvdata->spinlock); - - drvdata->clk = adev->pclk; - ret = clk_prepare_enable(drvdata->clk); - if (ret) - return ret; - - drvdata->buffer_depth = etb_get_buffer_depth(drvdata); - clk_disable_unprepare(drvdata->clk); - - if (drvdata->buffer_depth < 0) - return -EINVAL; - - drvdata->buf = devm_kzalloc(dev, - drvdata->buffer_depth * 4, GFP_KERNEL); - if (!drvdata->buf) - return -ENOMEM; - - desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL); - if (!desc) - return -ENOMEM; - - desc->type = CORESIGHT_DEV_TYPE_SINK; - desc->subtype.sink_subtype = CORESIGHT_DEV_SUBTYPE_SINK_BUFFER; - desc->ops = &etb_cs_ops; - desc->pdata = pdata; - desc->dev = dev; - desc->groups = coresight_etb_groups; - drvdata->csdev = coresight_register(desc); - if (IS_ERR(drvdata->csdev)) - return PTR_ERR(drvdata->csdev); - - drvdata->miscdev.name = pdata->name; - drvdata->miscdev.minor = MISC_DYNAMIC_MINOR; - drvdata->miscdev.fops = &etb_fops; - ret = misc_register(&drvdata->miscdev); - if (ret) - goto err_misc_register; - - dev_info(dev, "ETB initialized\n"); - return 0; - -err_misc_register: - coresight_unregister(drvdata->csdev); - 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; -} - -static struct amba_id etb_ids[] = { - { - .id = 0x0003b907, - .mask = 0x0003ffff, - }, - { 0, 0}, -}; - -static struct amba_driver etb_driver = { - .drv = { - .name = "coresight-etb10", - .owner = THIS_MODULE, - }, - .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"); diff --git a/drivers/coresight/coresight-etm-cp14.c b/drivers/coresight/coresight-etm-cp14.c deleted file mode 100644 index 12a2206..0000000 --- a/drivers/coresight/coresight-etm-cp14.c +++ /dev/null @@ -1,591 +0,0 @@ -/* Copyright (c) 2012, The Linux Foundation. All rights reserved. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 and - * only version 2 as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include - -#include "coresight-etm.h" - -int etm_readl_cp14(u32 reg, unsigned int *val) -{ - switch (reg) { - case ETMCR: - *val = etm_read(ETMCR); - return 0; - case ETMCCR: - *val = etm_read(ETMCCR); - return 0; - case ETMTRIGGER: - *val = etm_read(ETMTRIGGER); - return 0; - case ETMSR: - *val = etm_read(ETMSR); - return 0; - case ETMSCR: - *val = etm_read(ETMSCR); - return 0; - case ETMTSSCR: - *val = etm_read(ETMTSSCR); - return 0; - case ETMTEEVR: - *val = etm_read(ETMTEEVR); - return 0; - case ETMTECR1: - *val = etm_read(ETMTECR1); - return 0; - case ETMFFLR: - *val = etm_read(ETMFFLR); - return 0; - case ETMACVRn(0): - *val = etm_read(ETMACVR0); - return 0; - case ETMACVRn(1): - *val = etm_read(ETMACVR1); - return 0; - case ETMACVRn(2): - *val = etm_read(ETMACVR2); - return 0; - case ETMACVRn(3): - *val = etm_read(ETMACVR3); - return 0; - case ETMACVRn(4): - *val = etm_read(ETMACVR4); - return 0; - case ETMACVRn(5): - *val = etm_read(ETMACVR5); - return 0; - case ETMACVRn(6): - *val = etm_read(ETMACVR6); - return 0; - case ETMACVRn(7): - *val = etm_read(ETMACVR7); - return 0; - case ETMACVRn(8): - *val = etm_read(ETMACVR8); - return 0; - case ETMACVRn(9): - *val = etm_read(ETMACVR9); - return 0; - case ETMACVRn(10): - *val = etm_read(ETMACVR10); - return 0; - case ETMACVRn(11): - *val = etm_read(ETMACVR11); - return 0; - case ETMACVRn(12): - *val = etm_read(ETMACVR12); - return 0; - case ETMACVRn(13): - *val = etm_read(ETMACVR13); - return 0; - case ETMACVRn(14): - *val = etm_read(ETMACVR14); - return 0; - case ETMACVRn(15): - *val = etm_read(ETMACVR15); - return 0; - case ETMACTRn(0): - *val = etm_read(ETMACTR0); - return 0; - case ETMACTRn(1): - *val = etm_read(ETMACTR1); - return 0; - case ETMACTRn(2): - *val = etm_read(ETMACTR2); - return 0; - case ETMACTRn(3): - *val = etm_read(ETMACTR3); - return 0; - case ETMACTRn(4): - *val = etm_read(ETMACTR4); - return 0; - case ETMACTRn(5): - *val = etm_read(ETMACTR5); - return 0; - case ETMACTRn(6): - *val = etm_read(ETMACTR6); - return 0; - case ETMACTRn(7): - *val = etm_read(ETMACTR7); - return 0; - case ETMACTRn(8): - *val = etm_read(ETMACTR8); - return 0; - case ETMACTRn(9): - *val = etm_read(ETMACTR9); - return 0; - case ETMACTRn(10): - *val = etm_read(ETMACTR10); - return 0; - case ETMACTRn(11): - *val = etm_read(ETMACTR11); - return 0; - case ETMACTRn(12): - *val = etm_read(ETMACTR12); - return 0; - case ETMACTRn(13): - *val = etm_read(ETMACTR13); - return 0; - case ETMACTRn(14): - *val = etm_read(ETMACTR14); - return 0; - case ETMACTRn(15): - *val = etm_read(ETMACTR15); - return 0; - case ETMCNTRLDVRn(0): - *val = etm_read(ETMCNTRLDVR0); - return 0; - case ETMCNTRLDVRn(1): - *val = etm_read(ETMCNTRLDVR1); - return 0; - case ETMCNTRLDVRn(2): - *val = etm_read(ETMCNTRLDVR2); - return 0; - case ETMCNTRLDVRn(3): - *val = etm_read(ETMCNTRLDVR3); - return 0; - case ETMCNTENRn(0): - *val = etm_read(ETMCNTENR0); - return 0; - case ETMCNTENRn(1): - *val = etm_read(ETMCNTENR1); - return 0; - case ETMCNTENRn(2): - *val = etm_read(ETMCNTENR2); - return 0; - case ETMCNTENRn(3): - *val = etm_read(ETMCNTENR3); - return 0; - case ETMCNTRLDEVRn(0): - *val = etm_read(ETMCNTRLDEVR0); - return 0; - case ETMCNTRLDEVRn(1): - *val = etm_read(ETMCNTRLDEVR1); - return 0; - case ETMCNTRLDEVRn(2): - *val = etm_read(ETMCNTRLDEVR2); - return 0; - case ETMCNTRLDEVRn(3): - *val = etm_read(ETMCNTRLDEVR3); - return 0; - case ETMCNTVRn(0): - *val = etm_read(ETMCNTVR0); - return 0; - case ETMCNTVRn(1): - *val = etm_read(ETMCNTVR1); - return 0; - case ETMCNTVRn(2): - *val = etm_read(ETMCNTVR2); - return 0; - case ETMCNTVRn(3): - *val = etm_read(ETMCNTVR3); - return 0; - case ETMSQ12EVR: - *val = etm_read(ETMSQ12EVR); - return 0; - case ETMSQ21EVR: - *val = etm_read(ETMSQ21EVR); - return 0; - case ETMSQ23EVR: - *val = etm_read(ETMSQ23EVR); - return 0; - case ETMSQ31EVR: - *val = etm_read(ETMSQ31EVR); - return 0; - case ETMSQ32EVR: - *val = etm_read(ETMSQ32EVR); - return 0; - case ETMSQ13EVR: - *val = etm_read(ETMSQ13EVR); - return 0; - case ETMSQR: - *val = etm_read(ETMSQR); - return 0; - case ETMEXTOUTEVRn(0): - *val = etm_read(ETMEXTOUTEVR0); - return 0; - case ETMEXTOUTEVRn(1): - *val = etm_read(ETMEXTOUTEVR1); - return 0; - case ETMEXTOUTEVRn(2): - *val = etm_read(ETMEXTOUTEVR2); - return 0; - case ETMEXTOUTEVRn(3): - *val = etm_read(ETMEXTOUTEVR3); - return 0; - case ETMCIDCVRn(0): - *val = etm_read(ETMCIDCVR0); - return 0; - case ETMCIDCVRn(1): - *val = etm_read(ETMCIDCVR1); - return 0; - case ETMCIDCVRn(2): - *val = etm_read(ETMCIDCVR2); - return 0; - case ETMCIDCMR: - *val = etm_read(ETMCIDCMR); - return 0; - case ETMIMPSPEC0: - *val = etm_read(ETMIMPSPEC0); - return 0; - case ETMIMPSPEC1: - *val = etm_read(ETMIMPSPEC1); - return 0; - case ETMIMPSPEC2: - *val = etm_read(ETMIMPSPEC2); - return 0; - case ETMIMPSPEC3: - *val = etm_read(ETMIMPSPEC3); - return 0; - case ETMIMPSPEC4: - *val = etm_read(ETMIMPSPEC4); - return 0; - case ETMIMPSPEC5: - *val = etm_read(ETMIMPSPEC5); - return 0; - case ETMIMPSPEC6: - *val = etm_read(ETMIMPSPEC6); - return 0; - case ETMIMPSPEC7: - *val = etm_read(ETMIMPSPEC7); - return 0; - case ETMSYNCFR: - *val = etm_read(ETMSYNCFR); - return 0; - case ETMIDR: - *val = etm_read(ETMIDR); - return 0; - case ETMCCER: - *val = etm_read(ETMCCER); - return 0; - case ETMEXTINSELR: - *val = etm_read(ETMEXTINSELR); - return 0; - case ETMTESSEICR: - *val = etm_read(ETMTESSEICR); - return 0; - case ETMEIBCR: - *val = etm_read(ETMEIBCR); - return 0; - case ETMTSEVR: - *val = etm_read(ETMTSEVR); - return 0; - case ETMAUXCR: - *val = etm_read(ETMAUXCR); - return 0; - case ETMTRACEIDR: - *val = etm_read(ETMTRACEIDR); - return 0; - case ETMVMIDCVR: - *val = etm_read(ETMVMIDCVR); - return 0; - case ETMOSLSR: - *val = etm_read(ETMOSLSR); - return 0; - case ETMOSSRR: - *val = etm_read(ETMOSSRR); - return 0; - case ETMPDCR: - *val = etm_read(ETMPDCR); - return 0; - case ETMPDSR: - *val = etm_read(ETMPDSR); - return 0; - default: - *val = 0; - return -EINVAL; - } -} - -int etm_writel_cp14(u32 reg, u32 val) -{ - switch (reg) { - case ETMCR: - etm_write(val, ETMCR); - break; - case ETMTRIGGER: - etm_write(val, ETMTRIGGER); - break; - case ETMSR: - etm_write(val, ETMSR); - break; - case ETMTSSCR: - etm_write(val, ETMTSSCR); - break; - case ETMTEEVR: - etm_write(val, ETMTEEVR); - break; - case ETMTECR1: - etm_write(val, ETMTECR1); - break; - case ETMFFLR: - etm_write(val, ETMFFLR); - break; - case ETMACVRn(0): - etm_write(val, ETMACVR0); - break; - case ETMACVRn(1): - etm_write(val, ETMACVR1); - break; - case ETMACVRn(2): - etm_write(val, ETMACVR2); - break; - case ETMACVRn(3): - etm_write(val, ETMACVR3); - break; - case ETMACVRn(4): - etm_write(val, ETMACVR4); - break; - case ETMACVRn(5): - etm_write(val, ETMACVR5); - break; - case ETMACVRn(6): - etm_write(val, ETMACVR6); - break; - case ETMACVRn(7): - etm_write(val, ETMACVR7); - break; - case ETMACVRn(8): - etm_write(val, ETMACVR8); - break; - case ETMACVRn(9): - etm_write(val, ETMACVR9); - break; - case ETMACVRn(10): - etm_write(val, ETMACVR10); - break; - case ETMACVRn(11): - etm_write(val, ETMACVR11); - break; - case ETMACVRn(12): - etm_write(val, ETMACVR12); - break; - case ETMACVRn(13): - etm_write(val, ETMACVR13); - break; - case ETMACVRn(14): - etm_write(val, ETMACVR14); - break; - case ETMACVRn(15): - etm_write(val, ETMACVR15); - break; - case ETMACTRn(0): - etm_write(val, ETMACTR0); - break; - case ETMACTRn(1): - etm_write(val, ETMACTR1); - break; - case ETMACTRn(2): - etm_write(val, ETMACTR2); - break; - case ETMACTRn(3): - etm_write(val, ETMACTR3); - break; - case ETMACTRn(4): - etm_write(val, ETMACTR4); - break; - case ETMACTRn(5): - etm_write(val, ETMACTR5); - break; - case ETMACTRn(6): - etm_write(val, ETMACTR6); - break; - case ETMACTRn(7): - etm_write(val, ETMACTR7); - break; - case ETMACTRn(8): - etm_write(val, ETMACTR8); - break; - case ETMACTRn(9): - etm_write(val, ETMACTR9); - break; - case ETMACTRn(10): - etm_write(val, ETMACTR10); - break; - case ETMACTRn(11): - etm_write(val, ETMACTR11); - break; - case ETMACTRn(12): - etm_write(val, ETMACTR12); - break; - case ETMACTRn(13): - etm_write(val, ETMACTR13); - break; - case ETMACTRn(14): - etm_write(val, ETMACTR14); - break; - case ETMACTRn(15): - etm_write(val, ETMACTR15); - break; - case ETMCNTRLDVRn(0): - etm_write(val, ETMCNTRLDVR0); - break; - case ETMCNTRLDVRn(1): - etm_write(val, ETMCNTRLDVR1); - break; - case ETMCNTRLDVRn(2): - etm_write(val, ETMCNTRLDVR2); - break; - case ETMCNTRLDVRn(3): - etm_write(val, ETMCNTRLDVR3); - break; - case ETMCNTENRn(0): - etm_write(val, ETMCNTENR0); - break; - case ETMCNTENRn(1): - etm_write(val, ETMCNTENR1); - break; - case ETMCNTENRn(2): - etm_write(val, ETMCNTENR2); - break; - case ETMCNTENRn(3): - etm_write(val, ETMCNTENR3); - break; - case ETMCNTRLDEVRn(0): - etm_write(val, ETMCNTRLDEVR0); - break; - case ETMCNTRLDEVRn(1): - etm_write(val, ETMCNTRLDEVR1); - break; - case ETMCNTRLDEVRn(2): - etm_write(val, ETMCNTRLDEVR2); - break; - case ETMCNTRLDEVRn(3): - etm_write(val, ETMCNTRLDEVR3); - break; - case ETMCNTVRn(0): - etm_write(val, ETMCNTVR0); - break; - case ETMCNTVRn(1): - etm_write(val, ETMCNTVR1); - break; - case ETMCNTVRn(2): - etm_write(val, ETMCNTVR2); - break; - case ETMCNTVRn(3): - etm_write(val, ETMCNTVR3); - break; - case ETMSQ12EVR: - etm_write(val, ETMSQ12EVR); - break; - case ETMSQ21EVR: - etm_write(val, ETMSQ21EVR); - break; - case ETMSQ23EVR: - etm_write(val, ETMSQ23EVR); - break; - case ETMSQ31EVR: - etm_write(val, ETMSQ31EVR); - break; - case ETMSQ32EVR: - etm_write(val, ETMSQ32EVR); - break; - case ETMSQ13EVR: - etm_write(val, ETMSQ13EVR); - break; - case ETMSQR: - etm_write(val, ETMSQR); - break; - case ETMEXTOUTEVRn(0): - etm_write(val, ETMEXTOUTEVR0); - break; - case ETMEXTOUTEVRn(1): - etm_write(val, ETMEXTOUTEVR1); - break; - case ETMEXTOUTEVRn(2): - etm_write(val, ETMEXTOUTEVR2); - break; - case ETMEXTOUTEVRn(3): - etm_write(val, ETMEXTOUTEVR3); - break; - case ETMCIDCVRn(0): - etm_write(val, ETMCIDCVR0); - break; - case ETMCIDCVRn(1): - etm_write(val, ETMCIDCVR1); - break; - case ETMCIDCVRn(2): - etm_write(val, ETMCIDCVR2); - break; - case ETMCIDCMR: - etm_write(val, ETMCIDCMR); - break; - case ETMIMPSPEC0: - etm_write(val, ETMIMPSPEC0); - break; - case ETMIMPSPEC1: - etm_write(val, ETMIMPSPEC1); - break; - case ETMIMPSPEC2: - etm_write(val, ETMIMPSPEC2); - break; - case ETMIMPSPEC3: - etm_write(val, ETMIMPSPEC3); - break; - case ETMIMPSPEC4: - etm_write(val, ETMIMPSPEC4); - break; - case ETMIMPSPEC5: - etm_write(val, ETMIMPSPEC5); - break; - case ETMIMPSPEC6: - etm_write(val, ETMIMPSPEC6); - break; - case ETMIMPSPEC7: - etm_write(val, ETMIMPSPEC7); - break; - case ETMSYNCFR: - etm_write(val, ETMSYNCFR); - break; - case ETMEXTINSELR: - etm_write(val, ETMEXTINSELR); - break; - case ETMTESSEICR: - etm_write(val, ETMTESSEICR); - break; - case ETMEIBCR: - etm_write(val, ETMEIBCR); - break; - case ETMTSEVR: - etm_write(val, ETMTSEVR); - break; - case ETMAUXCR: - etm_write(val, ETMAUXCR); - break; - case ETMTRACEIDR: - etm_write(val, ETMTRACEIDR); - break; - case ETMVMIDCVR: - etm_write(val, ETMVMIDCVR); - break; - case ETMOSLAR: - etm_write(val, ETMOSLAR); - break; - case ETMOSSRR: - etm_write(val, ETMOSSRR); - break; - case ETMPDCR: - etm_write(val, ETMPDCR); - break; - case ETMPDSR: - etm_write(val, ETMPDSR); - break; - default: - return -EINVAL; - } - - return 0; -} diff --git a/drivers/coresight/coresight-etm.h b/drivers/coresight/coresight-etm.h deleted file mode 100644 index 501c5fa..0000000 --- a/drivers/coresight/coresight-etm.h +++ /dev/null @@ -1,251 +0,0 @@ -/* Copyright (c) 2014-2015, The Linux Foundation. All rights reserved. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 and - * only version 2 as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -#ifndef _CORESIGHT_CORESIGHT_ETM_H -#define _CORESIGHT_CORESIGHT_ETM_H - -#include -#include "coresight-priv.h" - -/* - * Device registers: - * 0x000 - 0x2FC: Trace registers - * 0x300 - 0x314: Management registers - * 0x318 - 0xEFC: Trace registers - * - * Coresight registers - * 0xF00 - 0xF9C: Management registers - * 0xFA0 - 0xFA4: Management registers in PFTv1.0 - * Trace registers in PFTv1.1 - * 0xFA8 - 0xFFC: Management registers - */ - -/* Trace registers (0x000-0x2FC) */ -#define ETMCR 0x000 -#define ETMCCR 0x004 -#define ETMTRIGGER 0x008 -#define ETMSR 0x010 -#define ETMSCR 0x014 -#define ETMTSSCR 0x018 -#define ETMTECR2 0x01c -#define ETMTEEVR 0x020 -#define ETMTECR1 0x024 -#define ETMFFLR 0x02c -#define ETMACVRn(n) (0x040 + (n * 4)) -#define ETMACTRn(n) (0x080 + (n * 4)) -#define ETMCNTRLDVRn(n) (0x140 + (n * 4)) -#define ETMCNTENRn(n) (0x150 + (n * 4)) -#define ETMCNTRLDEVRn(n) (0x160 + (n * 4)) -#define ETMCNTVRn(n) (0x170 + (n * 4)) -#define ETMSQ12EVR 0x180 -#define ETMSQ21EVR 0x184 -#define ETMSQ23EVR 0x188 -#define ETMSQ31EVR 0x18c -#define ETMSQ32EVR 0x190 -#define ETMSQ13EVR 0x194 -#define ETMSQR 0x19c -#define ETMEXTOUTEVRn(n) (0x1a0 + (n * 4)) -#define ETMCIDCVRn(n) (0x1b0 + (n * 4)) -#define ETMCIDCMR 0x1bc -#define ETMIMPSPEC0 0x1c0 -#define ETMIMPSPEC1 0x1c4 -#define ETMIMPSPEC2 0x1c8 -#define ETMIMPSPEC3 0x1cc -#define ETMIMPSPEC4 0x1d0 -#define ETMIMPSPEC5 0x1d4 -#define ETMIMPSPEC6 0x1d8 -#define ETMIMPSPEC7 0x1dc -#define ETMSYNCFR 0x1e0 -#define ETMIDR 0x1e4 -#define ETMCCER 0x1e8 -#define ETMEXTINSELR 0x1ec -#define ETMTESSEICR 0x1f0 -#define ETMEIBCR 0x1f4 -#define ETMTSEVR 0x1f8 -#define ETMAUXCR 0x1fc -#define ETMTRACEIDR 0x200 -#define ETMVMIDCVR 0x240 -/* Management registers (0x300-0x314) */ -#define ETMOSLAR 0x300 -#define ETMOSLSR 0x304 -#define ETMOSSRR 0x308 -#define ETMPDCR 0x310 -#define ETMPDSR 0x314 -#define ETM_MAX_ADDR_CMP 16 -#define ETM_MAX_CNTR 4 -#define ETM_MAX_CTXID_CMP 3 - -/* Register definition */ -/* ETMCR - 0x00 */ -#define ETMCR_PWD_DWN BIT(0) -#define ETMCR_STALL_MODE BIT(7) -#define ETMCR_ETM_PRG BIT(10) -#define ETMCR_ETM_EN BIT(11) -#define ETMCR_CYC_ACC BIT(12) -#define ETMCR_CTXID_SIZE (BIT(14)|BIT(15)) -#define ETMCR_TIMESTAMP_EN BIT(28) -/* ETMCCR - 0x04 */ -#define ETMCCR_FIFOFULL BIT(23) -/* ETMPDCR - 0x310 */ -#define ETMPDCR_PWD_UP BIT(3) -/* ETMTECR1 - 0x024 */ -#define ETMTECR1_ADDR_COMP_1 BIT(0) -#define ETMTECR1_INC_EXC BIT(24) -#define ETMTECR1_START_STOP BIT(25) -/* ETMCCER - 0x1E8 */ -#define ETMCCER_TIMESTAMP BIT(22) - -#define ETM_MODE_EXCLUDE BIT(0) -#define ETM_MODE_CYCACC BIT(1) -#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_SQR_MASK 0x3 -#define ETM_TRACEID_MASK 0x3f -#define ETM_EVENT_MASK 0x1ffff -#define ETM_SYNC_MASK 0xfff -#define ETM_ALL_MASK 0xffffffff - -#define ETMSR_PROG_BIT 1 -#define ETM_SEQ_STATE_MAX_VAL (0x2) -#define PORT_SIZE_MASK (GENMASK(21, 21) | GENMASK(6, 4)) - -#define ETM_HARD_WIRE_RES_A /* Hard wired, always true */ \ - ((0x0f << 0) | \ - /* Resource index A */ \ - (0x06 << 4)) - -#define ETM_ADD_COMP_0 /* Single addr comparator 1 */ \ - ((0x00 << 7) | \ - /* Resource index B */ \ - (0x00 << 11)) - -#define ETM_EVENT_NOT_A BIT(14) /* NOT(A) */ - -#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. - * @csdev: component vitals needed by the framework. - * @clk: the clock this component is associated to. - * @spinlock: only one at a time pls. - * @cpu: the cpu this component is affined to. - * @port_size: port size as reported by ETMCR bit 4-6 and 21. - * @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. - * @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. - * @fifofull_level: setting for register ETMFFLR. - * @addr_idx: index for the address comparator selection. - * @addr_val: value for address comparator register. - * @addr_acctype: access type for address comparator register. - * @addr_type: current status of the comparator register. - * @cntr_idx: index for the counter register selection. - * @cntr_rld_val: reload value of a counter register. - * @cntr_event: control for counter enable register. - * @cntr_rld_event: value for counter reload event register. - * @cntr_val: counter value register. - * @seq_12_event: event causing the transition from 1 to 2. - * @seq_21_event: event causing the transition from 2 to 1. - * @seq_23_event: event causing the transition from 2 to 3. - * @seq_31_event: event causing the transition from 3 to 1. - * @seq_32_event: event causing the transition from 3 to 2. - * @seq_13_event: event causing the transition from 1 to 3. - * @seq_curr_state: current value of the sequencer register. - * @ctxid_idx: index for the context ID registers. - * @ctxid_val: value for the context ID to trigger on. - * @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. - */ -struct etm_drvdata { - void __iomem *base; - struct device *dev; - struct coresight_device *csdev; - struct clk *clk; - 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; - u32 mode; - u32 ctrl; - u32 trigger_event; - u32 startstop_ctrl; - u32 enable_event; - u32 enable_ctrl1; - u32 fifofull_level; - u8 addr_idx; - u32 addr_val[ETM_MAX_ADDR_CMP]; - u32 addr_acctype[ETM_MAX_ADDR_CMP]; - u32 addr_type[ETM_MAX_ADDR_CMP]; - u8 cntr_idx; - u32 cntr_rld_val[ETM_MAX_CNTR]; - u32 cntr_event[ETM_MAX_CNTR]; - u32 cntr_rld_event[ETM_MAX_CNTR]; - u32 cntr_val[ETM_MAX_CNTR]; - u32 seq_12_event; - u32 seq_21_event; - u32 seq_23_event; - u32 seq_31_event; - u32 seq_32_event; - u32 seq_13_event; - u32 seq_curr_state; - u8 ctxid_idx; - u32 ctxid_val[ETM_MAX_CTXID_CMP]; - u32 ctxid_mask; - u32 sync_freq; - u32 timestamp_event; -}; - -enum etm_addr_type { - ETM_ADDR_TYPE_NONE, - ETM_ADDR_TYPE_SINGLE, - ETM_ADDR_TYPE_RANGE, - ETM_ADDR_TYPE_START, - ETM_ADDR_TYPE_STOP, -}; -#endif diff --git a/drivers/coresight/coresight-etm3x.c b/drivers/coresight/coresight-etm3x.c deleted file mode 100644 index c965f57..0000000 --- a/drivers/coresight/coresight-etm3x.c +++ /dev/null @@ -1,1932 +0,0 @@ -/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 and - * only version 2 as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "coresight-etm.h" - -static int boot_enable; -module_param_named(boot_enable, boot_enable, int, S_IRUGO); - -/* The number of ETM/PTM currently registered */ -static int etm_count; -static struct etm_drvdata *etmdrvdata[NR_CPUS]; - -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; -} - -/* - * Memory mapped writes to clear os lock are not supported on some processors - * and OS lock must be unlocked before any memory mapped access on such - * processors, otherwise memory mapped reads/writes will be invalid. - */ -static void etm_os_unlock(void *info) -{ - struct etm_drvdata *drvdata = (struct etm_drvdata *)info; - /* Writing any value to ETMOSLAR unlocks the trace registers */ - etm_writel(drvdata, 0x0, ETMOSLAR); - isb(); -} - -static void etm_set_pwrdwn(struct etm_drvdata *drvdata) -{ - u32 etmcr; - - /* Ensure pending cp14 accesses complete before setting pwrdwn */ - mb(); - isb(); - etmcr = etm_readl(drvdata, ETMCR); - etmcr |= ETMCR_PWD_DWN; - etm_writel(drvdata, etmcr, ETMCR); -} - -static void etm_clr_pwrdwn(struct etm_drvdata *drvdata) -{ - u32 etmcr; - - etmcr = etm_readl(drvdata, ETMCR); - etmcr &= ~ETMCR_PWD_DWN; - etm_writel(drvdata, etmcr, ETMCR); - /* Ensure pwrup completes before subsequent cp14 accesses */ - mb(); - isb(); -} - -static void etm_set_pwrup(struct etm_drvdata *drvdata) -{ - u32 etmpdcr; - - etmpdcr = readl_relaxed(drvdata->base + ETMPDCR); - etmpdcr |= ETMPDCR_PWD_UP; - writel_relaxed(etmpdcr, drvdata->base + ETMPDCR); - /* Ensure pwrup completes before subsequent cp14 accesses */ - mb(); - isb(); -} - -static void etm_clr_pwrup(struct etm_drvdata *drvdata) -{ - u32 etmpdcr; - - /* Ensure pending cp14 accesses complete before clearing pwrup */ - mb(); - isb(); - etmpdcr = readl_relaxed(drvdata->base + ETMPDCR); - etmpdcr &= ~ETMPDCR_PWD_UP; - writel_relaxed(etmpdcr, drvdata->base + ETMPDCR); -} - -/** - * coresight_timeout_etm - loop until a bit has changed to a specific state. - * @drvdata: etm's private data structure. - * @offset: address of a register, starting from @addr. - * @position: the position of the bit of interest. - * @value: the value the bit should have. - * - * Basically the same as @coresight_timeout except for the register access - * method where we have to account for CP14 configurations. - - * Return: 0 as soon as the bit has taken the desired state or -EAGAIN if - * TIMEOUT_US has elapsed, which ever happens first. - */ - -static int coresight_timeout_etm(struct etm_drvdata *drvdata, u32 offset, - int position, int value) -{ - int i; - u32 val; - - for (i = TIMEOUT_US; i > 0; i--) { - val = etm_readl(drvdata, offset); - /* Waiting on the bit to go from 0 to 1 */ - if (value) { - if (val & BIT(position)) - return 0; - /* Waiting on the bit to go from 1 to 0 */ - } else { - if (!(val & BIT(position))) - return 0; - } - - /* - * Delay is arbitrary - the specification doesn't say how long - * we are expected to wait. Extra check required to make sure - * we don't wait needlessly on the last iteration. - */ - if (i - 1) - udelay(1); - } - - return -EAGAIN; -} - - -static void etm_set_prog(struct etm_drvdata *drvdata) -{ - u32 etmcr; - - etmcr = etm_readl(drvdata, ETMCR); - etmcr |= ETMCR_ETM_PRG; - etm_writel(drvdata, etmcr, ETMCR); - /* - * Recommended by spec for cp14 accesses to ensure etmcr write is - * complete before polling etmsr - */ - isb(); - if (coresight_timeout_etm(drvdata, ETMSR, ETMSR_PROG_BIT, 1)) { - dev_err(drvdata->dev, - "timeout observed when probing at offset %#x\n", ETMSR); - } -} - -static void etm_clr_prog(struct etm_drvdata *drvdata) -{ - u32 etmcr; - - etmcr = etm_readl(drvdata, ETMCR); - etmcr &= ~ETMCR_ETM_PRG; - etm_writel(drvdata, etmcr, ETMCR); - /* - * Recommended by spec for cp14 accesses to ensure etmcr write is - * complete before polling etmsr - */ - isb(); - if (coresight_timeout_etm(drvdata, ETMSR, ETMSR_PROG_BIT, 0)) { - dev_err(drvdata->dev, - "timeout observed when probing at offset %#x\n", ETMSR); - } -} - -static void etm_set_default(struct etm_drvdata *drvdata) -{ - int i; - - drvdata->trigger_event = ETM_DEFAULT_EVENT_VAL; - drvdata->enable_event = ETM_HARD_WIRE_RES_A; - - drvdata->seq_12_event = ETM_DEFAULT_EVENT_VAL; - drvdata->seq_21_event = ETM_DEFAULT_EVENT_VAL; - drvdata->seq_23_event = ETM_DEFAULT_EVENT_VAL; - drvdata->seq_31_event = ETM_DEFAULT_EVENT_VAL; - drvdata->seq_32_event = ETM_DEFAULT_EVENT_VAL; - drvdata->seq_13_event = ETM_DEFAULT_EVENT_VAL; - drvdata->timestamp_event = ETM_DEFAULT_EVENT_VAL; - - for (i = 0; i < drvdata->nr_cntr; i++) { - drvdata->cntr_rld_val[i] = 0x0; - drvdata->cntr_event[i] = ETM_DEFAULT_EVENT_VAL; - drvdata->cntr_rld_event[i] = ETM_DEFAULT_EVENT_VAL; - drvdata->cntr_val[i] = 0x0; - } - - drvdata->seq_curr_state = 0x0; - drvdata->ctxid_idx = 0x0; - for (i = 0; i < drvdata->nr_ctxid_cmp; i++) - drvdata->ctxid_val[i] = 0x0; - drvdata->ctxid_mask = 0x0; -} - -static void etm_enable_hw(void *info) -{ - int i; - u32 etmcr; - struct etm_drvdata *drvdata = info; - - CS_UNLOCK(drvdata->base); - - /* Turn engine on */ - etm_clr_pwrdwn(drvdata); - /* Apply power to trace registers */ - etm_set_pwrup(drvdata); - /* Make sure all registers are accessible */ - etm_os_unlock(drvdata); - - etm_set_prog(drvdata); - - etmcr = etm_readl(drvdata, ETMCR); - etmcr &= (ETMCR_PWD_DWN | ETMCR_ETM_PRG); - etmcr |= drvdata->port_size; - etm_writel(drvdata, drvdata->ctrl | etmcr, ETMCR); - etm_writel(drvdata, drvdata->trigger_event, ETMTRIGGER); - etm_writel(drvdata, drvdata->startstop_ctrl, ETMTSSCR); - etm_writel(drvdata, drvdata->enable_event, ETMTEEVR); - etm_writel(drvdata, drvdata->enable_ctrl1, ETMTECR1); - etm_writel(drvdata, drvdata->fifofull_level, ETMFFLR); - for (i = 0; i < drvdata->nr_addr_cmp; i++) { - etm_writel(drvdata, drvdata->addr_val[i], ETMACVRn(i)); - etm_writel(drvdata, drvdata->addr_acctype[i], ETMACTRn(i)); - } - for (i = 0; i < drvdata->nr_cntr; i++) { - etm_writel(drvdata, drvdata->cntr_rld_val[i], ETMCNTRLDVRn(i)); - etm_writel(drvdata, drvdata->cntr_event[i], ETMCNTENRn(i)); - etm_writel(drvdata, drvdata->cntr_rld_event[i], - ETMCNTRLDEVRn(i)); - etm_writel(drvdata, drvdata->cntr_val[i], ETMCNTVRn(i)); - } - etm_writel(drvdata, drvdata->seq_12_event, ETMSQ12EVR); - etm_writel(drvdata, drvdata->seq_21_event, ETMSQ21EVR); - etm_writel(drvdata, drvdata->seq_23_event, ETMSQ23EVR); - etm_writel(drvdata, drvdata->seq_31_event, ETMSQ31EVR); - etm_writel(drvdata, drvdata->seq_32_event, ETMSQ32EVR); - etm_writel(drvdata, drvdata->seq_13_event, ETMSQ13EVR); - etm_writel(drvdata, drvdata->seq_curr_state, ETMSQR); - for (i = 0; i < drvdata->nr_ext_out; i++) - etm_writel(drvdata, ETM_DEFAULT_EVENT_VAL, ETMEXTOUTEVRn(i)); - for (i = 0; i < drvdata->nr_ctxid_cmp; i++) - etm_writel(drvdata, drvdata->ctxid_val[i], ETMCIDCVRn(i)); - etm_writel(drvdata, drvdata->ctxid_mask, ETMCIDCMR); - etm_writel(drvdata, drvdata->sync_freq, ETMSYNCFR); - /* No external input selected */ - etm_writel(drvdata, 0x0, ETMEXTINSELR); - etm_writel(drvdata, drvdata->timestamp_event, ETMTSEVR); - /* No auxiliary control selected */ - etm_writel(drvdata, 0x0, ETMAUXCR); - etm_writel(drvdata, drvdata->traceid, ETMTRACEIDR); - /* No VMID comparator value selected */ - etm_writel(drvdata, 0x0, ETMVMIDCVR); - - /* Ensures trace output is enabled from this ETM */ - etm_writel(drvdata, drvdata->ctrl | ETMCR_ETM_EN | etmcr, ETMCR); - - etm_clr_prog(drvdata); - CS_LOCK(drvdata->base); - - dev_dbg(drvdata->dev, "cpu: %d enable smp call done\n", drvdata->cpu); -} - -static int etm_trace_id_simple(struct etm_drvdata *drvdata) -{ - if (!drvdata->enable) - return drvdata->traceid; - - return (etm_readl(drvdata, ETMTRACEIDR) & ETM_TRACEID_MASK); -} - -static int etm_trace_id(struct coresight_device *csdev) -{ - struct etm_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); - unsigned long flags; - int trace_id = -1; - - if (!drvdata->enable) - return drvdata->traceid; - - if (clk_prepare_enable(drvdata->clk)) - goto out; - - spin_lock_irqsave(&drvdata->spinlock, flags); - - CS_UNLOCK(drvdata->base); - trace_id = (etm_readl(drvdata, ETMTRACEIDR) & ETM_TRACEID_MASK); - CS_LOCK(drvdata->base); - - spin_unlock_irqrestore(&drvdata->spinlock, flags); - clk_disable_unprepare(drvdata->clk); -out: - return trace_id; -} - -static int etm_enable(struct coresight_device *csdev) -{ - struct etm_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); - int ret; - - ret = clk_prepare_enable(drvdata->clk); - if (ret) - goto err_clk; - - spin_lock(&drvdata->spinlock); - - /* - * Configure the ETM only if the CPU is online. If it isn't online - * hw configuration will take place when 'CPU_STARTING' is received - * in @etm_cpu_callback. - */ - if (cpu_online(drvdata->cpu)) { - ret = smp_call_function_single(drvdata->cpu, - etm_enable_hw, drvdata, 1); - if (ret) - goto err; - } - - drvdata->enable = true; - drvdata->sticky_enable = true; - - spin_unlock(&drvdata->spinlock); - - dev_info(drvdata->dev, "ETM tracing enabled\n"); - return 0; -err: - spin_unlock(&drvdata->spinlock); - clk_disable_unprepare(drvdata->clk); -err_clk: - return ret; -} - -static void etm_disable_hw(void *info) -{ - int i; - struct etm_drvdata *drvdata = info; - - CS_UNLOCK(drvdata->base); - etm_set_prog(drvdata); - - /* Program trace enable to low by using always false event */ - etm_writel(drvdata, ETM_HARD_WIRE_RES_A | ETM_EVENT_NOT_A, ETMTEEVR); - - /* Read back sequencer and counters for post trace analysis */ - drvdata->seq_curr_state = (etm_readl(drvdata, ETMSQR) & ETM_SQR_MASK); - - for (i = 0; i < drvdata->nr_cntr; i++) - drvdata->cntr_val[i] = etm_readl(drvdata, ETMCNTVRn(i)); - - etm_set_pwrdwn(drvdata); - CS_LOCK(drvdata->base); - - dev_dbg(drvdata->dev, "cpu: %d disable smp call done\n", drvdata->cpu); -} - -static void etm_disable(struct coresight_device *csdev) -{ - struct etm_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); - - /* - * Taking hotplug lock here protects from clocks getting disabled - * with tracing being left on (crash scenario) if user disable occurs - * after cpu online mask indicates the cpu is offline but before the - * DYING hotplug callback is serviced by the ETM driver. - */ - get_online_cpus(); - spin_lock(&drvdata->spinlock); - - /* - * Executing etm_disable_hw on the cpu whose ETM is being disabled - * ensures that register writes occur when cpu is powered. - */ - smp_call_function_single(drvdata->cpu, etm_disable_hw, drvdata, 1); - drvdata->enable = false; - - spin_unlock(&drvdata->spinlock); - put_online_cpus(); - - clk_disable_unprepare(drvdata->clk); - - dev_info(drvdata->dev, "ETM tracing disabled\n"); -} - -static const struct coresight_ops_source etm_source_ops = { - .trace_id = etm_trace_id, - .enable = etm_enable, - .disable = etm_disable, -}; - -static const struct coresight_ops etm_cs_ops = { - .source_ops = &etm_source_ops, -}; - -static ssize_t nr_addr_cmp_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - unsigned long val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - val = drvdata->nr_addr_cmp; - return sprintf(buf, "%#lx\n", val); -} -static DEVICE_ATTR_RO(nr_addr_cmp); - -static ssize_t nr_cntr_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ unsigned long val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - val = drvdata->nr_cntr; - return sprintf(buf, "%#lx\n", val); -} -static DEVICE_ATTR_RO(nr_cntr); - -static ssize_t nr_ctxid_cmp_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - unsigned long val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - val = drvdata->nr_ctxid_cmp; - return sprintf(buf, "%#lx\n", val); -} -static DEVICE_ATTR_RO(nr_ctxid_cmp); - -static ssize_t etmsr_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - int ret; - unsigned long flags, val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - ret = clk_prepare_enable(drvdata->clk); - if (ret) - return ret; - - spin_lock_irqsave(&drvdata->spinlock, flags); - CS_UNLOCK(drvdata->base); - - val = etm_readl(drvdata, ETMSR); - - CS_LOCK(drvdata->base); - spin_unlock_irqrestore(&drvdata->spinlock, flags); - clk_disable_unprepare(drvdata->clk); - - return sprintf(buf, "%#lx\n", val); -} -static DEVICE_ATTR_RO(etmsr); - -static ssize_t reset_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t size) -{ - int i, ret; - unsigned long val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - ret = kstrtoul(buf, 16, &val); - if (ret) - return ret; - - if (val) { - spin_lock(&drvdata->spinlock); - drvdata->mode = ETM_MODE_EXCLUDE; - drvdata->ctrl = 0x0; - drvdata->trigger_event = ETM_DEFAULT_EVENT_VAL; - drvdata->startstop_ctrl = 0x0; - drvdata->addr_idx = 0x0; - for (i = 0; i < drvdata->nr_addr_cmp; i++) { - drvdata->addr_val[i] = 0x0; - drvdata->addr_acctype[i] = 0x0; - drvdata->addr_type[i] = ETM_ADDR_TYPE_NONE; - } - drvdata->cntr_idx = 0x0; - - etm_set_default(drvdata); - spin_unlock(&drvdata->spinlock); - } - - return size; -} -static DEVICE_ATTR_WO(reset); - -static ssize_t mode_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - unsigned long val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - val = drvdata->mode; - return sprintf(buf, "%#lx\n", val); -} - -static ssize_t mode_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t size) -{ - int ret; - unsigned long val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - ret = kstrtoul(buf, 16, &val); - if (ret) - return ret; - - spin_lock(&drvdata->spinlock); - drvdata->mode = val & ETM_MODE_ALL; - - if (drvdata->mode & ETM_MODE_EXCLUDE) - drvdata->enable_ctrl1 |= ETMTECR1_INC_EXC; - else - drvdata->enable_ctrl1 &= ~ETMTECR1_INC_EXC; - - if (drvdata->mode & ETM_MODE_CYCACC) - drvdata->ctrl |= ETMCR_CYC_ACC; - else - drvdata->ctrl &= ~ETMCR_CYC_ACC; - - if (drvdata->mode & ETM_MODE_STALL) { - if (!(drvdata->etmccr & ETMCCR_FIFOFULL)) { - dev_warn(drvdata->dev, "stall mode not supported\n"); - ret = -EINVAL; - goto err_unlock; - } - drvdata->ctrl |= ETMCR_STALL_MODE; - } else - drvdata->ctrl &= ~ETMCR_STALL_MODE; - - if (drvdata->mode & ETM_MODE_TIMESTAMP) { - if (!(drvdata->etmccer & ETMCCER_TIMESTAMP)) { - dev_warn(drvdata->dev, "timestamp not supported\n"); - ret = -EINVAL; - goto err_unlock; - } - drvdata->ctrl |= ETMCR_TIMESTAMP_EN; - } else - drvdata->ctrl &= ~ETMCR_TIMESTAMP_EN; - - if (drvdata->mode & ETM_MODE_CTXID) - drvdata->ctrl |= ETMCR_CTXID_SIZE; - else - drvdata->ctrl &= ~ETMCR_CTXID_SIZE; - spin_unlock(&drvdata->spinlock); - - return size; - -err_unlock: - spin_unlock(&drvdata->spinlock); - return ret; -} -static DEVICE_ATTR_RW(mode); - -static ssize_t trigger_event_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - unsigned long val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - val = drvdata->trigger_event; - return sprintf(buf, "%#lx\n", val); -} - -static ssize_t trigger_event_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t size) -{ - int ret; - unsigned long val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - ret = kstrtoul(buf, 16, &val); - if (ret) - return ret; - - drvdata->trigger_event = val & ETM_EVENT_MASK; - - return size; -} -static DEVICE_ATTR_RW(trigger_event); - -static ssize_t enable_event_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - unsigned long val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - val = drvdata->enable_event; - return sprintf(buf, "%#lx\n", val); -} - -static ssize_t enable_event_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t size) -{ - int ret; - unsigned long val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - ret = kstrtoul(buf, 16, &val); - if (ret) - return ret; - - drvdata->enable_event = val & ETM_EVENT_MASK; - - return size; -} -static DEVICE_ATTR_RW(enable_event); - -static ssize_t fifofull_level_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - unsigned long val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - val = drvdata->fifofull_level; - return sprintf(buf, "%#lx\n", val); -} - -static ssize_t fifofull_level_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t size) -{ - int ret; - unsigned long val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - ret = kstrtoul(buf, 16, &val); - if (ret) - return ret; - - drvdata->fifofull_level = val; - - return size; -} -static DEVICE_ATTR_RW(fifofull_level); - -static ssize_t addr_idx_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - unsigned long val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - val = drvdata->addr_idx; - return sprintf(buf, "%#lx\n", val); -} - -static ssize_t addr_idx_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t size) -{ - int ret; - unsigned long val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - ret = kstrtoul(buf, 16, &val); - if (ret) - return ret; - - if (val >= drvdata->nr_addr_cmp) - return -EINVAL; - - /* - * Use spinlock to ensure index doesn't change while it gets - * dereferenced multiple times within a spinlock block elsewhere. - */ - spin_lock(&drvdata->spinlock); - drvdata->addr_idx = val; - spin_unlock(&drvdata->spinlock); - - return size; -} -static DEVICE_ATTR_RW(addr_idx); - -static ssize_t addr_single_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - u8 idx; - unsigned long val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - spin_lock(&drvdata->spinlock); - idx = drvdata->addr_idx; - if (!(drvdata->addr_type[idx] == ETM_ADDR_TYPE_NONE || - drvdata->addr_type[idx] == ETM_ADDR_TYPE_SINGLE)) { - spin_unlock(&drvdata->spinlock); - return -EINVAL; - } - - val = drvdata->addr_val[idx]; - spin_unlock(&drvdata->spinlock); - - return sprintf(buf, "%#lx\n", val); -} - -static ssize_t addr_single_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t size) -{ - u8 idx; - int ret; - unsigned long val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - ret = kstrtoul(buf, 16, &val); - if (ret) - return ret; - - spin_lock(&drvdata->spinlock); - idx = drvdata->addr_idx; - if (!(drvdata->addr_type[idx] == ETM_ADDR_TYPE_NONE || - drvdata->addr_type[idx] == ETM_ADDR_TYPE_SINGLE)) { - spin_unlock(&drvdata->spinlock); - return -EINVAL; - } - - drvdata->addr_val[idx] = val; - drvdata->addr_type[idx] = ETM_ADDR_TYPE_SINGLE; - spin_unlock(&drvdata->spinlock); - - return size; -} -static DEVICE_ATTR_RW(addr_single); - -static ssize_t addr_range_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - u8 idx; - unsigned long val1, val2; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - spin_lock(&drvdata->spinlock); - idx = drvdata->addr_idx; - if (idx % 2 != 0) { - spin_unlock(&drvdata->spinlock); - return -EPERM; - } - if (!((drvdata->addr_type[idx] == ETM_ADDR_TYPE_NONE && - drvdata->addr_type[idx + 1] == ETM_ADDR_TYPE_NONE) || - (drvdata->addr_type[idx] == ETM_ADDR_TYPE_RANGE && - drvdata->addr_type[idx + 1] == ETM_ADDR_TYPE_RANGE))) { - spin_unlock(&drvdata->spinlock); - return -EPERM; - } - - val1 = drvdata->addr_val[idx]; - val2 = drvdata->addr_val[idx + 1]; - spin_unlock(&drvdata->spinlock); - - return sprintf(buf, "%#lx %#lx\n", val1, val2); -} - -static ssize_t addr_range_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t size) -{ - u8 idx; - unsigned long val1, val2; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - if (sscanf(buf, "%lx %lx", &val1, &val2) != 2) - return -EINVAL; - /* Lower address comparator cannot have a higher address value */ - if (val1 > val2) - return -EINVAL; - - spin_lock(&drvdata->spinlock); - idx = drvdata->addr_idx; - if (idx % 2 != 0) { - spin_unlock(&drvdata->spinlock); - return -EPERM; - } - if (!((drvdata->addr_type[idx] == ETM_ADDR_TYPE_NONE && - drvdata->addr_type[idx + 1] == ETM_ADDR_TYPE_NONE) || - (drvdata->addr_type[idx] == ETM_ADDR_TYPE_RANGE && - drvdata->addr_type[idx + 1] == ETM_ADDR_TYPE_RANGE))) { - spin_unlock(&drvdata->spinlock); - return -EPERM; - } - - drvdata->addr_val[idx] = val1; - drvdata->addr_type[idx] = ETM_ADDR_TYPE_RANGE; - drvdata->addr_val[idx + 1] = val2; - drvdata->addr_type[idx + 1] = ETM_ADDR_TYPE_RANGE; - drvdata->enable_ctrl1 |= (1 << (idx/2)); - spin_unlock(&drvdata->spinlock); - - return size; -} -static DEVICE_ATTR_RW(addr_range); - -static ssize_t addr_start_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - u8 idx; - unsigned long val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - spin_lock(&drvdata->spinlock); - idx = drvdata->addr_idx; - if (!(drvdata->addr_type[idx] == ETM_ADDR_TYPE_NONE || - drvdata->addr_type[idx] == ETM_ADDR_TYPE_START)) { - spin_unlock(&drvdata->spinlock); - return -EPERM; - } - - val = drvdata->addr_val[idx]; - spin_unlock(&drvdata->spinlock); - - return sprintf(buf, "%#lx\n", val); -} - -static ssize_t addr_start_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t size) -{ - u8 idx; - int ret; - unsigned long val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - ret = kstrtoul(buf, 16, &val); - if (ret) - return ret; - - spin_lock(&drvdata->spinlock); - idx = drvdata->addr_idx; - if (!(drvdata->addr_type[idx] == ETM_ADDR_TYPE_NONE || - drvdata->addr_type[idx] == ETM_ADDR_TYPE_START)) { - spin_unlock(&drvdata->spinlock); - return -EPERM; - } - - drvdata->addr_val[idx] = val; - drvdata->addr_type[idx] = ETM_ADDR_TYPE_START; - drvdata->startstop_ctrl |= (1 << idx); - drvdata->enable_ctrl1 |= BIT(25); - spin_unlock(&drvdata->spinlock); - - return size; -} -static DEVICE_ATTR_RW(addr_start); - -static ssize_t addr_stop_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - u8 idx; - unsigned long val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - spin_lock(&drvdata->spinlock); - idx = drvdata->addr_idx; - if (!(drvdata->addr_type[idx] == ETM_ADDR_TYPE_NONE || - drvdata->addr_type[idx] == ETM_ADDR_TYPE_STOP)) { - spin_unlock(&drvdata->spinlock); - return -EPERM; - } - - val = drvdata->addr_val[idx]; - spin_unlock(&drvdata->spinlock); - - return sprintf(buf, "%#lx\n", val); -} - -static ssize_t addr_stop_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t size) -{ - u8 idx; - int ret; - unsigned long val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - ret = kstrtoul(buf, 16, &val); - if (ret) - return ret; - - spin_lock(&drvdata->spinlock); - idx = drvdata->addr_idx; - if (!(drvdata->addr_type[idx] == ETM_ADDR_TYPE_NONE || - drvdata->addr_type[idx] == ETM_ADDR_TYPE_STOP)) { - spin_unlock(&drvdata->spinlock); - return -EPERM; - } - - drvdata->addr_val[idx] = val; - drvdata->addr_type[idx] = ETM_ADDR_TYPE_STOP; - drvdata->startstop_ctrl |= (1 << (idx + 16)); - drvdata->enable_ctrl1 |= ETMTECR1_START_STOP; - spin_unlock(&drvdata->spinlock); - - return size; -} -static DEVICE_ATTR_RW(addr_stop); - -static ssize_t addr_acctype_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - unsigned long val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - spin_lock(&drvdata->spinlock); - val = drvdata->addr_acctype[drvdata->addr_idx]; - spin_unlock(&drvdata->spinlock); - - return sprintf(buf, "%#lx\n", val); -} - -static ssize_t addr_acctype_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t size) -{ - int ret; - unsigned long val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - ret = kstrtoul(buf, 16, &val); - if (ret) - return ret; - - spin_lock(&drvdata->spinlock); - drvdata->addr_acctype[drvdata->addr_idx] = val; - spin_unlock(&drvdata->spinlock); - - return size; -} -static DEVICE_ATTR_RW(addr_acctype); - -static ssize_t cntr_idx_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - unsigned long val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - val = drvdata->cntr_idx; - return sprintf(buf, "%#lx\n", val); -} - -static ssize_t cntr_idx_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t size) -{ - int ret; - unsigned long val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - ret = kstrtoul(buf, 16, &val); - if (ret) - return ret; - - if (val >= drvdata->nr_cntr) - return -EINVAL; - /* - * Use spinlock to ensure index doesn't change while it gets - * dereferenced multiple times within a spinlock block elsewhere. - */ - spin_lock(&drvdata->spinlock); - drvdata->cntr_idx = val; - spin_unlock(&drvdata->spinlock); - - return size; -} -static DEVICE_ATTR_RW(cntr_idx); - -static ssize_t cntr_rld_val_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - unsigned long val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - spin_lock(&drvdata->spinlock); - val = drvdata->cntr_rld_val[drvdata->cntr_idx]; - spin_unlock(&drvdata->spinlock); - - return sprintf(buf, "%#lx\n", val); -} - -static ssize_t cntr_rld_val_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t size) -{ - int ret; - unsigned long val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - ret = kstrtoul(buf, 16, &val); - if (ret) - return ret; - - spin_lock(&drvdata->spinlock); - drvdata->cntr_rld_val[drvdata->cntr_idx] = val; - spin_unlock(&drvdata->spinlock); - - return size; -} -static DEVICE_ATTR_RW(cntr_rld_val); - -static ssize_t cntr_event_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - unsigned long val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - spin_lock(&drvdata->spinlock); - val = drvdata->cntr_event[drvdata->cntr_idx]; - spin_unlock(&drvdata->spinlock); - - return sprintf(buf, "%#lx\n", val); -} - -static ssize_t cntr_event_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t size) -{ - int ret; - unsigned long val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - ret = kstrtoul(buf, 16, &val); - if (ret) - return ret; - - spin_lock(&drvdata->spinlock); - drvdata->cntr_event[drvdata->cntr_idx] = val & ETM_EVENT_MASK; - spin_unlock(&drvdata->spinlock); - - return size; -} -static DEVICE_ATTR_RW(cntr_event); - -static ssize_t cntr_rld_event_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - unsigned long val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - spin_lock(&drvdata->spinlock); - val = drvdata->cntr_rld_event[drvdata->cntr_idx]; - spin_unlock(&drvdata->spinlock); - - return sprintf(buf, "%#lx\n", val); -} - -static ssize_t cntr_rld_event_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t size) -{ - int ret; - unsigned long val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - ret = kstrtoul(buf, 16, &val); - if (ret) - return ret; - - spin_lock(&drvdata->spinlock); - drvdata->cntr_rld_event[drvdata->cntr_idx] = val & ETM_EVENT_MASK; - spin_unlock(&drvdata->spinlock); - - return size; -} -static DEVICE_ATTR_RW(cntr_rld_event); - -static ssize_t cntr_val_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - int i, ret = 0; - u32 val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - if (!drvdata->enable) { - spin_lock(&drvdata->spinlock); - for (i = 0; i < drvdata->nr_cntr; i++) - ret += sprintf(buf, "counter %d: %x\n", - i, drvdata->cntr_val[i]); - spin_unlock(&drvdata->spinlock); - return ret; - } - - for (i = 0; i < drvdata->nr_cntr; i++) { - val = etm_readl(drvdata, ETMCNTVRn(i)); - ret += sprintf(buf, "counter %d: %x\n", i, val); - } - - return ret; -} - -static ssize_t cntr_val_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t size) -{ - int ret; - unsigned long val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - ret = kstrtoul(buf, 16, &val); - if (ret) - return ret; - - spin_lock(&drvdata->spinlock); - drvdata->cntr_val[drvdata->cntr_idx] = val; - spin_unlock(&drvdata->spinlock); - - return size; -} -static DEVICE_ATTR_RW(cntr_val); - -static ssize_t seq_12_event_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - unsigned long val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - val = drvdata->seq_12_event; - return sprintf(buf, "%#lx\n", val); -} - -static ssize_t seq_12_event_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t size) -{ - int ret; - unsigned long val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - ret = kstrtoul(buf, 16, &val); - if (ret) - return ret; - - drvdata->seq_12_event = val & ETM_EVENT_MASK; - return size; -} -static DEVICE_ATTR_RW(seq_12_event); - -static ssize_t seq_21_event_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - unsigned long val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - val = drvdata->seq_21_event; - return sprintf(buf, "%#lx\n", val); -} - -static ssize_t seq_21_event_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t size) -{ - int ret; - unsigned long val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - ret = kstrtoul(buf, 16, &val); - if (ret) - return ret; - - drvdata->seq_21_event = val & ETM_EVENT_MASK; - return size; -} -static DEVICE_ATTR_RW(seq_21_event); - -static ssize_t seq_23_event_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - unsigned long val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - val = drvdata->seq_23_event; - return sprintf(buf, "%#lx\n", val); -} - -static ssize_t seq_23_event_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t size) -{ - int ret; - unsigned long val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - ret = kstrtoul(buf, 16, &val); - if (ret) - return ret; - - drvdata->seq_23_event = val & ETM_EVENT_MASK; - return size; -} -static DEVICE_ATTR_RW(seq_23_event); - -static ssize_t seq_31_event_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - unsigned long val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - val = drvdata->seq_31_event; - return sprintf(buf, "%#lx\n", val); -} - -static ssize_t seq_31_event_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t size) -{ - int ret; - unsigned long val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - ret = kstrtoul(buf, 16, &val); - if (ret) - return ret; - - drvdata->seq_31_event = val & ETM_EVENT_MASK; - return size; -} -static DEVICE_ATTR_RW(seq_31_event); - -static ssize_t seq_32_event_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - unsigned long val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - val = drvdata->seq_32_event; - return sprintf(buf, "%#lx\n", val); -} - -static ssize_t seq_32_event_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t size) -{ - int ret; - unsigned long val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - ret = kstrtoul(buf, 16, &val); - if (ret) - return ret; - - drvdata->seq_32_event = val & ETM_EVENT_MASK; - return size; -} -static DEVICE_ATTR_RW(seq_32_event); - -static ssize_t seq_13_event_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - unsigned long val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - val = drvdata->seq_13_event; - return sprintf(buf, "%#lx\n", val); -} - -static ssize_t seq_13_event_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t size) -{ - int ret; - unsigned long val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - ret = kstrtoul(buf, 16, &val); - if (ret) - return ret; - - drvdata->seq_13_event = val & ETM_EVENT_MASK; - return size; -} -static DEVICE_ATTR_RW(seq_13_event); - -static ssize_t seq_curr_state_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - int ret; - unsigned long val, flags; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - if (!drvdata->enable) { - val = drvdata->seq_curr_state; - goto out; - } - - ret = clk_prepare_enable(drvdata->clk); - if (ret) - return ret; - - spin_lock_irqsave(&drvdata->spinlock, flags); - - CS_UNLOCK(drvdata->base); - val = (etm_readl(drvdata, ETMSQR) & ETM_SQR_MASK); - CS_LOCK(drvdata->base); - - spin_unlock_irqrestore(&drvdata->spinlock, flags); - clk_disable_unprepare(drvdata->clk); -out: - return sprintf(buf, "%#lx\n", val); -} - -static ssize_t seq_curr_state_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t size) -{ - int ret; - unsigned long val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - ret = kstrtoul(buf, 16, &val); - if (ret) - return ret; - - if (val > ETM_SEQ_STATE_MAX_VAL) - return -EINVAL; - - drvdata->seq_curr_state = val; - - return size; -} -static DEVICE_ATTR_RW(seq_curr_state); - -static ssize_t ctxid_idx_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - unsigned long val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - val = drvdata->ctxid_idx; - return sprintf(buf, "%#lx\n", val); -} - -static ssize_t ctxid_idx_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t size) -{ - int ret; - unsigned long val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - ret = kstrtoul(buf, 16, &val); - if (ret) - return ret; - - if (val >= drvdata->nr_ctxid_cmp) - return -EINVAL; - - /* - * Use spinlock to ensure index doesn't change while it gets - * dereferenced multiple times within a spinlock block elsewhere. - */ - spin_lock(&drvdata->spinlock); - drvdata->ctxid_idx = val; - spin_unlock(&drvdata->spinlock); - - return size; -} -static DEVICE_ATTR_RW(ctxid_idx); - -static ssize_t ctxid_val_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - unsigned long val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - spin_lock(&drvdata->spinlock); - val = drvdata->ctxid_val[drvdata->ctxid_idx]; - spin_unlock(&drvdata->spinlock); - - return sprintf(buf, "%#lx\n", val); -} - -static ssize_t ctxid_val_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t size) -{ - int ret; - unsigned long val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - ret = kstrtoul(buf, 16, &val); - if (ret) - return ret; - - spin_lock(&drvdata->spinlock); - drvdata->ctxid_val[drvdata->ctxid_idx] = val; - spin_unlock(&drvdata->spinlock); - - return size; -} -static DEVICE_ATTR_RW(ctxid_val); - -static ssize_t ctxid_mask_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - unsigned long val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - val = drvdata->ctxid_mask; - return sprintf(buf, "%#lx\n", val); -} - -static ssize_t ctxid_mask_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t size) -{ - int ret; - unsigned long val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - ret = kstrtoul(buf, 16, &val); - if (ret) - return ret; - - drvdata->ctxid_mask = val; - return size; -} -static DEVICE_ATTR_RW(ctxid_mask); - -static ssize_t sync_freq_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - unsigned long val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - val = drvdata->sync_freq; - return sprintf(buf, "%#lx\n", val); -} - -static ssize_t sync_freq_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t size) -{ - int ret; - unsigned long val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - ret = kstrtoul(buf, 16, &val); - if (ret) - return ret; - - drvdata->sync_freq = val & ETM_SYNC_MASK; - return size; -} -static DEVICE_ATTR_RW(sync_freq); - -static ssize_t timestamp_event_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - unsigned long val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - val = drvdata->timestamp_event; - return sprintf(buf, "%#lx\n", val); -} - -static ssize_t timestamp_event_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t size) -{ - int ret; - unsigned long val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - ret = kstrtoul(buf, 16, &val); - if (ret) - return ret; - - drvdata->timestamp_event = val & ETM_EVENT_MASK; - return size; -} -static DEVICE_ATTR_RW(timestamp_event); - -static ssize_t status_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - int ret; - unsigned long flags; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - ret = clk_prepare_enable(drvdata->clk); - if (ret) - return ret; - - spin_lock_irqsave(&drvdata->spinlock, flags); - - CS_UNLOCK(drvdata->base); - ret = sprintf(buf, - "ETMCCR: 0x%08x\n" - "ETMCCER: 0x%08x\n" - "ETMSCR: 0x%08x\n" - "ETMIDR: 0x%08x\n" - "ETMCR: 0x%08x\n" - "ETMTRACEIDR: 0x%08x\n" - "Enable event: 0x%08x\n" - "Enable start/stop: 0x%08x\n" - "Enable control: CR1 0x%08x CR2 0x%08x\n" - "CPU affinity: %d\n", - drvdata->etmccr, drvdata->etmccer, - etm_readl(drvdata, ETMSCR), etm_readl(drvdata, ETMIDR), - etm_readl(drvdata, ETMCR), etm_trace_id_simple(drvdata), - etm_readl(drvdata, ETMTEEVR), - etm_readl(drvdata, ETMTSSCR), - etm_readl(drvdata, ETMTECR1), - etm_readl(drvdata, ETMTECR2), - drvdata->cpu); - CS_LOCK(drvdata->base); - - spin_unlock_irqrestore(&drvdata->spinlock, flags); - clk_disable_unprepare(drvdata->clk); - - return ret; -} -static DEVICE_ATTR_RO(status); - -static ssize_t traceid_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - int ret; - unsigned long val, flags; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - if (!drvdata->enable) { - val = drvdata->traceid; - goto out; - } - - ret = clk_prepare_enable(drvdata->clk); - if (ret) - return ret; - - spin_lock_irqsave(&drvdata->spinlock, flags); - CS_UNLOCK(drvdata->base); - - val = (etm_readl(drvdata, ETMTRACEIDR) & ETM_TRACEID_MASK); - - CS_LOCK(drvdata->base); - spin_unlock_irqrestore(&drvdata->spinlock, flags); - clk_disable_unprepare(drvdata->clk); -out: - return sprintf(buf, "%#lx\n", val); -} - -static ssize_t traceid_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t size) -{ - int ret; - unsigned long val; - struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); - - ret = kstrtoul(buf, 16, &val); - if (ret) - return ret; - - drvdata->traceid = val & ETM_TRACEID_MASK; - return size; -} -static DEVICE_ATTR_RW(traceid); - -static struct attribute *coresight_etm_attrs[] = { - &dev_attr_nr_addr_cmp.attr, - &dev_attr_nr_cntr.attr, - &dev_attr_nr_ctxid_cmp.attr, - &dev_attr_etmsr.attr, - &dev_attr_reset.attr, - &dev_attr_mode.attr, - &dev_attr_trigger_event.attr, - &dev_attr_enable_event.attr, - &dev_attr_fifofull_level.attr, - &dev_attr_addr_idx.attr, - &dev_attr_addr_single.attr, - &dev_attr_addr_range.attr, - &dev_attr_addr_start.attr, - &dev_attr_addr_stop.attr, - &dev_attr_addr_acctype.attr, - &dev_attr_cntr_idx.attr, - &dev_attr_cntr_rld_val.attr, - &dev_attr_cntr_event.attr, - &dev_attr_cntr_rld_event.attr, - &dev_attr_cntr_val.attr, - &dev_attr_seq_12_event.attr, - &dev_attr_seq_21_event.attr, - &dev_attr_seq_23_event.attr, - &dev_attr_seq_31_event.attr, - &dev_attr_seq_32_event.attr, - &dev_attr_seq_13_event.attr, - &dev_attr_seq_curr_state.attr, - &dev_attr_ctxid_idx.attr, - &dev_attr_ctxid_val.attr, - &dev_attr_ctxid_mask.attr, - &dev_attr_sync_freq.attr, - &dev_attr_timestamp_event.attr, - &dev_attr_status.attr, - &dev_attr_traceid.attr, - NULL, -}; -ATTRIBUTE_GROUPS(coresight_etm); - -static int etm_cpu_callback(struct notifier_block *nfb, unsigned long action, - void *hcpu) -{ - unsigned int cpu = (unsigned long)hcpu; - - if (!etmdrvdata[cpu]) - goto out; - - switch (action & (~CPU_TASKS_FROZEN)) { - case CPU_STARTING: - spin_lock(&etmdrvdata[cpu]->spinlock); - if (!etmdrvdata[cpu]->os_unlock) { - etm_os_unlock(etmdrvdata[cpu]); - etmdrvdata[cpu]->os_unlock = true; - } - - if (etmdrvdata[cpu]->enable) - etm_enable_hw(etmdrvdata[cpu]); - spin_unlock(&etmdrvdata[cpu]->spinlock); - break; - - case CPU_ONLINE: - if (etmdrvdata[cpu]->boot_enable && - !etmdrvdata[cpu]->sticky_enable) - coresight_enable(etmdrvdata[cpu]->csdev); - break; - - case CPU_DYING: - spin_lock(&etmdrvdata[cpu]->spinlock); - if (etmdrvdata[cpu]->enable) - etm_disable_hw(etmdrvdata[cpu]); - spin_unlock(&etmdrvdata[cpu]->spinlock); - break; - } -out: - return NOTIFY_OK; -} - -static struct notifier_block etm_cpu_notifier = { - .notifier_call = etm_cpu_callback, -}; - -static bool etm_arch_supported(u8 arch) -{ - switch (arch) { - case ETM_ARCH_V3_3: - break; - case ETM_ARCH_V3_5: - break; - case PFT_ARCH_V1_0: - break; - case PFT_ARCH_V1_1: - break; - default: - return false; - } - return true; -} - -static void etm_init_arch_data(void *info) -{ - u32 etmidr; - u32 etmccr; - struct etm_drvdata *drvdata = info; - - CS_UNLOCK(drvdata->base); - - /* First dummy read */ - (void)etm_readl(drvdata, ETMPDSR); - /* Provide power to ETM: ETMPDCR[3] == 1 */ - etm_set_pwrup(drvdata); - /* - * Clear power down bit since when this bit is set writes to - * certain registers might be ignored. - */ - etm_clr_pwrdwn(drvdata); - /* - * Set prog bit. It will be set from reset but this is included to - * ensure it is set - */ - etm_set_prog(drvdata); - - /* Find all capabilities */ - etmidr = etm_readl(drvdata, ETMIDR); - drvdata->arch = BMVAL(etmidr, 4, 11); - drvdata->port_size = etm_readl(drvdata, ETMCR) & PORT_SIZE_MASK; - - drvdata->etmccer = etm_readl(drvdata, ETMCCER); - etmccr = etm_readl(drvdata, ETMCCR); - drvdata->etmccr = etmccr; - drvdata->nr_addr_cmp = BMVAL(etmccr, 0, 3) * 2; - drvdata->nr_cntr = BMVAL(etmccr, 13, 15); - drvdata->nr_ext_inp = BMVAL(etmccr, 17, 19); - drvdata->nr_ext_out = BMVAL(etmccr, 20, 22); - drvdata->nr_ctxid_cmp = BMVAL(etmccr, 24, 25); - - etm_set_pwrdwn(drvdata); - etm_clr_pwrup(drvdata); - CS_LOCK(drvdata->base); -} - -static void etm_init_default_data(struct etm_drvdata *drvdata) -{ - /* - * A trace ID of value 0 is invalid, so let's start at some - * random value that fits in 7 bits and will be just as good. - */ - static int etm3x_traceid = 0x10; - - u32 flags = (1 << 0 | /* instruction execute*/ - 3 << 3 | /* ARM instruction */ - 0 << 5 | /* No data value comparison */ - 0 << 7 | /* No exact mach */ - 0 << 8 | /* Ignore context ID */ - 0 << 10); /* Security ignored */ - - /* - * Initial configuration only - guarantees sources handled by - * this driver have a unique ID at startup time but not between - * all other types of sources. For that we lean on the core - * framework. - */ - drvdata->traceid = etm3x_traceid++; - drvdata->ctrl = (ETMCR_CYC_ACC | ETMCR_TIMESTAMP_EN); - drvdata->enable_ctrl1 = ETMTECR1_ADDR_COMP_1; - if (drvdata->nr_addr_cmp >= 2) { - drvdata->addr_val[0] = (u32) _stext; - drvdata->addr_val[1] = (u32) _etext; - drvdata->addr_acctype[0] = flags; - drvdata->addr_acctype[1] = flags; - drvdata->addr_type[0] = ETM_ADDR_TYPE_RANGE; - drvdata->addr_type[1] = ETM_ADDR_TYPE_RANGE; - } - - etm_set_default(drvdata); -} - -static int etm_probe(struct amba_device *adev, const struct amba_id *id) -{ - int ret; - void __iomem *base; - struct device *dev = &adev->dev; - struct coresight_platform_data *pdata = NULL; - struct etm_drvdata *drvdata; - struct resource *res = &adev->res; - struct coresight_desc *desc; - struct device_node *np = adev->dev.of_node; - - desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL); - if (!desc) - return -ENOMEM; - - drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); - if (!drvdata) - return -ENOMEM; - - if (np) { - pdata = of_get_coresight_platform_data(dev, np); - if (IS_ERR(pdata)) - return PTR_ERR(pdata); - - adev->dev.platform_data = pdata; - drvdata->use_cp14 = of_property_read_bool(np, "arm,cp14"); - } - - drvdata->dev = &adev->dev; - dev_set_drvdata(dev, drvdata); - - /* Validity for the resource is already checked by the AMBA core */ - base = devm_ioremap_resource(dev, res); - if (IS_ERR(base)) - return PTR_ERR(base); - - drvdata->base = base; - - spin_lock_init(&drvdata->spinlock); - - drvdata->clk = adev->pclk; - ret = clk_prepare_enable(drvdata->clk); - if (ret) - return ret; - - drvdata->cpu = pdata ? pdata->cpu : 0; - - get_online_cpus(); - etmdrvdata[drvdata->cpu] = drvdata; - - if (!smp_call_function_single(drvdata->cpu, etm_os_unlock, drvdata, 1)) - drvdata->os_unlock = true; - - if (smp_call_function_single(drvdata->cpu, - etm_init_arch_data, drvdata, 1)) - dev_err(dev, "ETM arch init failed\n"); - - if (!etm_count++) - register_hotcpu_notifier(&etm_cpu_notifier); - - put_online_cpus(); - - if (etm_arch_supported(drvdata->arch) == false) { - ret = -EINVAL; - goto err_arch_supported; - } - etm_init_default_data(drvdata); - - clk_disable_unprepare(drvdata->clk); - - desc->type = CORESIGHT_DEV_TYPE_SOURCE; - desc->subtype.source_subtype = CORESIGHT_DEV_SUBTYPE_SOURCE_PROC; - desc->ops = &etm_cs_ops; - desc->pdata = pdata; - desc->dev = dev; - desc->groups = coresight_etm_groups; - drvdata->csdev = coresight_register(desc); - if (IS_ERR(drvdata->csdev)) { - ret = PTR_ERR(drvdata->csdev); - goto err_arch_supported; - } - - dev_info(dev, "ETM initialized\n"); - - if (boot_enable) { - coresight_enable(drvdata->csdev); - drvdata->boot_enable = true; - } - - return 0; - -err_arch_supported: - clk_disable_unprepare(drvdata->clk); - if (--etm_count == 0) - unregister_hotcpu_notifier(&etm_cpu_notifier); - return ret; -} - -static int etm_remove(struct amba_device *adev) -{ - struct etm_drvdata *drvdata = amba_get_drvdata(adev); - - coresight_unregister(drvdata->csdev); - if (--etm_count == 0) - unregister_hotcpu_notifier(&etm_cpu_notifier); - - return 0; -} - -static struct amba_id etm_ids[] = { - { /* ETM 3.3 */ - .id = 0x0003b921, - .mask = 0x0003ffff, - }, - { /* ETM 3.5 */ - .id = 0x0003b956, - .mask = 0x0003ffff, - }, - { /* PTM 1.0 */ - .id = 0x0003b950, - .mask = 0x0003ffff, - }, - { /* PTM 1.1 */ - .id = 0x0003b95f, - .mask = 0x0003ffff, - }, - { 0, 0}, -}; - -static struct amba_driver etm_driver = { - .drv = { - .name = "coresight-etm3x", - .owner = THIS_MODULE, - }, - .probe = etm_probe, - .remove = etm_remove, - .id_table = etm_ids, -}; - -int __init etm_init(void) -{ - return amba_driver_register(&etm_driver); -} -module_init(etm_init); - -void __exit etm_exit(void) -{ - amba_driver_unregister(&etm_driver); -} -module_exit(etm_exit); - -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("CoreSight Program Flow Trace driver"); diff --git a/drivers/coresight/coresight-funnel.c b/drivers/coresight/coresight-funnel.c deleted file mode 100644 index 3db36f7..0000000 --- a/drivers/coresight/coresight-funnel.c +++ /dev/null @@ -1,258 +0,0 @@ -/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 and - * only version 2 as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "coresight-priv.h" - -#define FUNNEL_FUNCTL 0x000 -#define FUNNEL_PRICTL 0x004 - -#define FUNNEL_HOLDTIME_MASK 0xf00 -#define FUNNEL_HOLDTIME_SHFT 0x8 -#define FUNNEL_HOLDTIME (0x7 << FUNNEL_HOLDTIME_SHFT) - -/** - * struct funnel_drvdata - specifics associated to a funnel component - * @base: memory mapped base address for this component. - * @dev: the device entity associated to this component. - * @csdev: component vitals needed by the framework. - * @clk: the clock this component is associated to. - * @priority: port selection order. - */ -struct funnel_drvdata { - void __iomem *base; - struct device *dev; - struct coresight_device *csdev; - struct clk *clk; - unsigned long priority; -}; - -static void funnel_enable_hw(struct funnel_drvdata *drvdata, int port) -{ - u32 functl; - - CS_UNLOCK(drvdata->base); - - functl = readl_relaxed(drvdata->base + FUNNEL_FUNCTL); - functl &= ~FUNNEL_HOLDTIME_MASK; - functl |= FUNNEL_HOLDTIME; - functl |= (1 << port); - writel_relaxed(functl, drvdata->base + FUNNEL_FUNCTL); - writel_relaxed(drvdata->priority, drvdata->base + FUNNEL_PRICTL); - - CS_LOCK(drvdata->base); -} - -static int funnel_enable(struct coresight_device *csdev, int inport, - int outport) -{ - struct funnel_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); - int ret; - - ret = clk_prepare_enable(drvdata->clk); - if (ret) - return ret; - - funnel_enable_hw(drvdata, inport); - - dev_info(drvdata->dev, "FUNNEL inport %d enabled\n", inport); - return 0; -} - -static void funnel_disable_hw(struct funnel_drvdata *drvdata, int inport) -{ - u32 functl; - - CS_UNLOCK(drvdata->base); - - functl = readl_relaxed(drvdata->base + FUNNEL_FUNCTL); - functl &= ~(1 << inport); - writel_relaxed(functl, drvdata->base + FUNNEL_FUNCTL); - - CS_LOCK(drvdata->base); -} - -static void funnel_disable(struct coresight_device *csdev, int inport, - int outport) -{ - struct funnel_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); - - funnel_disable_hw(drvdata, inport); - - clk_disable_unprepare(drvdata->clk); - - dev_info(drvdata->dev, "FUNNEL inport %d disabled\n", inport); -} - -static const struct coresight_ops_link funnel_link_ops = { - .enable = funnel_enable, - .disable = funnel_disable, -}; - -static const struct coresight_ops funnel_cs_ops = { - .link_ops = &funnel_link_ops, -}; - -static ssize_t priority_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct funnel_drvdata *drvdata = dev_get_drvdata(dev->parent); - unsigned long val = drvdata->priority; - - return sprintf(buf, "%#lx\n", val); -} - -static ssize_t priority_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t size) -{ - int ret; - unsigned long val; - struct funnel_drvdata *drvdata = dev_get_drvdata(dev->parent); - - ret = kstrtoul(buf, 16, &val); - if (ret) - return ret; - - drvdata->priority = val; - return size; -} -static DEVICE_ATTR_RW(priority); - -static u32 get_funnel_ctrl_hw(struct funnel_drvdata *drvdata) -{ - u32 functl; - - CS_UNLOCK(drvdata->base); - functl = readl_relaxed(drvdata->base + FUNNEL_FUNCTL); - CS_LOCK(drvdata->base); - - return functl; -} - -static ssize_t funnel_ctrl_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - int ret; - u32 val; - struct funnel_drvdata *drvdata = dev_get_drvdata(dev->parent); - - ret = clk_prepare_enable(drvdata->clk); - if (ret) - return ret; - - val = get_funnel_ctrl_hw(drvdata); - clk_disable_unprepare(drvdata->clk); - - return sprintf(buf, "%#x\n", val); -} -static DEVICE_ATTR_RO(funnel_ctrl); - -static struct attribute *coresight_funnel_attrs[] = { - &dev_attr_funnel_ctrl.attr, - &dev_attr_priority.attr, - NULL, -}; -ATTRIBUTE_GROUPS(coresight_funnel); - -static int funnel_probe(struct amba_device *adev, const struct amba_id *id) -{ - void __iomem *base; - struct device *dev = &adev->dev; - struct coresight_platform_data *pdata = NULL; - struct funnel_drvdata *drvdata; - struct resource *res = &adev->res; - struct coresight_desc *desc; - struct device_node *np = adev->dev.of_node; - - if (np) { - pdata = of_get_coresight_platform_data(dev, np); - if (IS_ERR(pdata)) - return PTR_ERR(pdata); - adev->dev.platform_data = pdata; - } - - drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); - if (!drvdata) - return -ENOMEM; - - drvdata->dev = &adev->dev; - dev_set_drvdata(dev, drvdata); - - /* Validity for the resource is already checked by the AMBA core */ - base = devm_ioremap_resource(dev, res); - if (IS_ERR(base)) - return PTR_ERR(base); - - drvdata->base = base; - - drvdata->clk = adev->pclk; - - desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL); - if (!desc) - return -ENOMEM; - - desc->type = CORESIGHT_DEV_TYPE_LINK; - desc->subtype.link_subtype = CORESIGHT_DEV_SUBTYPE_LINK_MERG; - desc->ops = &funnel_cs_ops; - desc->pdata = pdata; - desc->dev = dev; - desc->groups = coresight_funnel_groups; - drvdata->csdev = coresight_register(desc); - if (IS_ERR(drvdata->csdev)) - return PTR_ERR(drvdata->csdev); - - dev_info(dev, "FUNNEL initialized\n"); - return 0; -} - -static int funnel_remove(struct amba_device *adev) -{ - struct funnel_drvdata *drvdata = amba_get_drvdata(adev); - - coresight_unregister(drvdata->csdev); - return 0; -} - -static struct amba_id funnel_ids[] = { - { - .id = 0x0003b908, - .mask = 0x0003ffff, - }, - { 0, 0}, -}; - -static struct amba_driver funnel_driver = { - .drv = { - .name = "coresight-funnel", - .owner = THIS_MODULE, - }, - .probe = funnel_probe, - .remove = funnel_remove, - .id_table = funnel_ids, -}; - -module_amba_driver(funnel_driver); - -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("CoreSight Funnel driver"); diff --git a/drivers/coresight/coresight-priv.h b/drivers/coresight/coresight-priv.h deleted file mode 100644 index 62fcd98..0000000 --- a/drivers/coresight/coresight-priv.h +++ /dev/null @@ -1,63 +0,0 @@ -/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 and - * only version 2 as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -#ifndef _CORESIGHT_PRIV_H -#define _CORESIGHT_PRIV_H - -#include -#include -#include - -/* - * Coresight management registers (0xf00-0xfcc) - * 0xfa0 - 0xfa4: Management registers in PFTv1.0 - * Trace registers in PFTv1.1 - */ -#define CORESIGHT_ITCTRL 0xf00 -#define CORESIGHT_CLAIMSET 0xfa0 -#define CORESIGHT_CLAIMCLR 0xfa4 -#define CORESIGHT_LAR 0xfb0 -#define CORESIGHT_LSR 0xfb4 -#define CORESIGHT_AUTHSTATUS 0xfb8 -#define CORESIGHT_DEVID 0xfc8 -#define CORESIGHT_DEVTYPE 0xfcc - -#define TIMEOUT_US 100 -#define BMVAL(val, lsb, msb) ((val & GENMASK(msb, lsb)) >> lsb) - -static inline void CS_LOCK(void __iomem *addr) -{ - do { - /* Wait for things to settle */ - mb(); - writel_relaxed(0x0, addr + CORESIGHT_LAR); - } while (0); -} - -static inline void CS_UNLOCK(void __iomem *addr) -{ - do { - writel_relaxed(CORESIGHT_UNLOCK, addr + CORESIGHT_LAR); - /* Make sure everyone has seen this */ - mb(); - } while (0); -} - -#ifdef CONFIG_CORESIGHT_SOURCE_ETM3X -extern int etm_readl_cp14(u32 off, unsigned int *val); -extern int etm_writel_cp14(u32 off, u32 val); -#else -static inline int etm_readl_cp14(u32 off, unsigned int *val) { return 0; } -static inline int etm_writel_cp14(u32 off, u32 val) { return 0; } -#endif - -#endif diff --git a/drivers/coresight/coresight-replicator.c b/drivers/coresight/coresight-replicator.c deleted file mode 100644 index 75b9abd..0000000 --- a/drivers/coresight/coresight-replicator.c +++ /dev/null @@ -1,137 +0,0 @@ -/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 and - * only version 2 as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "coresight-priv.h" - -/** - * struct replicator_drvdata - specifics associated to a replicator component - * @dev: the device entity associated with this component - * @csdev: component vitals needed by the framework - */ -struct replicator_drvdata { - struct device *dev; - struct coresight_device *csdev; -}; - -static int replicator_enable(struct coresight_device *csdev, int inport, - int outport) -{ - struct replicator_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); - - dev_info(drvdata->dev, "REPLICATOR enabled\n"); - return 0; -} - -static void replicator_disable(struct coresight_device *csdev, int inport, - int outport) -{ - struct replicator_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); - - dev_info(drvdata->dev, "REPLICATOR disabled\n"); -} - -static const struct coresight_ops_link replicator_link_ops = { - .enable = replicator_enable, - .disable = replicator_disable, -}; - -static const struct coresight_ops replicator_cs_ops = { - .link_ops = &replicator_link_ops, -}; - -static int replicator_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct coresight_platform_data *pdata = NULL; - struct replicator_drvdata *drvdata; - struct coresight_desc *desc; - struct device_node *np = pdev->dev.of_node; - - if (np) { - pdata = of_get_coresight_platform_data(dev, np); - if (IS_ERR(pdata)) - return PTR_ERR(pdata); - pdev->dev.platform_data = pdata; - } - - drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); - if (!drvdata) - return -ENOMEM; - - drvdata->dev = &pdev->dev; - platform_set_drvdata(pdev, drvdata); - - desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL); - if (!desc) - return -ENOMEM; - - desc->type = CORESIGHT_DEV_TYPE_LINK; - desc->subtype.link_subtype = CORESIGHT_DEV_SUBTYPE_LINK_SPLIT; - desc->ops = &replicator_cs_ops; - desc->pdata = pdev->dev.platform_data; - desc->dev = &pdev->dev; - drvdata->csdev = coresight_register(desc); - if (IS_ERR(drvdata->csdev)) - return PTR_ERR(drvdata->csdev); - - dev_info(dev, "REPLICATOR initialized\n"); - return 0; -} - -static int replicator_remove(struct platform_device *pdev) -{ - struct replicator_drvdata *drvdata = platform_get_drvdata(pdev); - - coresight_unregister(drvdata->csdev); - return 0; -} - -static const struct of_device_id replicator_match[] = { - {.compatible = "arm,coresight-replicator"}, - {} -}; - -static struct platform_driver replicator_driver = { - .probe = replicator_probe, - .remove = replicator_remove, - .driver = { - .name = "coresight-replicator", - .of_match_table = replicator_match, - }, -}; - -static int __init replicator_init(void) -{ - return platform_driver_register(&replicator_driver); -} -module_init(replicator_init); - -static void __exit replicator_exit(void) -{ - platform_driver_unregister(&replicator_driver); -} -module_exit(replicator_exit); - -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("CoreSight Replicator driver"); diff --git a/drivers/coresight/coresight-tmc.c b/drivers/coresight/coresight-tmc.c deleted file mode 100644 index 7147f3d..0000000 --- a/drivers/coresight/coresight-tmc.c +++ /dev/null @@ -1,822 +0,0 @@ -/* Copyright (c) 2012, The Linux Foundation. All rights reserved. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 and - * only version 2 as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "coresight-priv.h" - -#define TMC_RSZ 0x004 -#define TMC_STS 0x00c -#define TMC_RRD 0x010 -#define TMC_RRP 0x014 -#define TMC_RWP 0x018 -#define TMC_TRG 0x01c -#define TMC_CTL 0x020 -#define TMC_RWD 0x024 -#define TMC_MODE 0x028 -#define TMC_LBUFLEVEL 0x02c -#define TMC_CBUFLEVEL 0x030 -#define TMC_BUFWM 0x034 -#define TMC_RRPHI 0x038 -#define TMC_RWPHI 0x03c -#define TMC_AXICTL 0x110 -#define TMC_DBALO 0x118 -#define TMC_DBAHI 0x11c -#define TMC_FFSR 0x300 -#define TMC_FFCR 0x304 -#define TMC_PSCR 0x308 -#define TMC_ITMISCOP0 0xee0 -#define TMC_ITTRFLIN 0xee8 -#define TMC_ITATBDATA0 0xeec -#define TMC_ITATBCTR2 0xef0 -#define TMC_ITATBCTR1 0xef4 -#define TMC_ITATBCTR0 0xef8 - -/* register description */ -/* TMC_CTL - 0x020 */ -#define TMC_CTL_CAPT_EN BIT(0) -/* TMC_STS - 0x00C */ -#define TMC_STS_TRIGGERED BIT(1) -/* TMC_AXICTL - 0x110 */ -#define TMC_AXICTL_PROT_CTL_B0 BIT(0) -#define TMC_AXICTL_PROT_CTL_B1 BIT(1) -#define TMC_AXICTL_SCT_GAT_MODE BIT(7) -#define TMC_AXICTL_WR_BURST_LEN 0xF00 -/* TMC_FFCR - 0x304 */ -#define TMC_FFCR_EN_FMT BIT(0) -#define TMC_FFCR_EN_TI BIT(1) -#define TMC_FFCR_FON_FLIN BIT(4) -#define TMC_FFCR_FON_TRIG_EVT BIT(5) -#define TMC_FFCR_FLUSHMAN BIT(6) -#define TMC_FFCR_TRIGON_TRIGIN BIT(8) -#define TMC_FFCR_STOP_ON_FLUSH BIT(12) - -#define TMC_STS_TRIGGERED_BIT 2 -#define TMC_FFCR_FLUSHMAN_BIT 6 - -enum tmc_config_type { - TMC_CONFIG_TYPE_ETB, - TMC_CONFIG_TYPE_ETR, - TMC_CONFIG_TYPE_ETF, -}; - -enum tmc_mode { - TMC_MODE_CIRCULAR_BUFFER, - TMC_MODE_SOFTWARE_FIFO, - TMC_MODE_HARDWARE_FIFO, -}; - -enum tmc_mem_intf_width { - TMC_MEM_INTF_WIDTH_32BITS = 0x2, - TMC_MEM_INTF_WIDTH_64BITS = 0x3, - TMC_MEM_INTF_WIDTH_128BITS = 0x4, - TMC_MEM_INTF_WIDTH_256BITS = 0x5, -}; - -/** - * struct tmc_drvdata - specifics associated to an TMC component - * @base: memory mapped base address for this component. - * @dev: the device entity associated to this component. - * @csdev: component vitals needed by the framework. - * @miscdev: specifics to handle "/dev/xyz.tmc" entry. - * @clk: the clock this component is associated to. - * @spinlock: only one at a time pls. - * @read_count: manages preparation of buffer for reading. - * @buf: area of memory where trace data get sent. - * @paddr: DMA start location in RAM. - * @vaddr: virtual representation of @paddr. - * @size: @buf size. - * @enable: this TMC is being used. - * @config_type: TMC variant, must be of type @tmc_config_type. - * @trigger_cntr: amount of words to store after a trigger. - */ -struct tmc_drvdata { - void __iomem *base; - struct device *dev; - struct coresight_device *csdev; - struct miscdevice miscdev; - struct clk *clk; - spinlock_t spinlock; - int read_count; - bool reading; - char *buf; - dma_addr_t paddr; - void __iomem *vaddr; - u32 size; - bool enable; - enum tmc_config_type config_type; - u32 trigger_cntr; -}; - -static void tmc_wait_for_ready(struct tmc_drvdata *drvdata) -{ - /* Ensure formatter, unformatter and hardware fifo are empty */ - if (coresight_timeout(drvdata->base, - TMC_STS, TMC_STS_TRIGGERED_BIT, 1)) { - dev_err(drvdata->dev, - "timeout observed when probing at offset %#x\n", - TMC_STS); - } -} - -static void tmc_flush_and_stop(struct tmc_drvdata *drvdata) -{ - u32 ffcr; - - ffcr = readl_relaxed(drvdata->base + TMC_FFCR); - ffcr |= TMC_FFCR_STOP_ON_FLUSH; - writel_relaxed(ffcr, drvdata->base + TMC_FFCR); - ffcr |= TMC_FFCR_FLUSHMAN; - writel_relaxed(ffcr, drvdata->base + TMC_FFCR); - /* Ensure flush completes */ - if (coresight_timeout(drvdata->base, - TMC_FFCR, TMC_FFCR_FLUSHMAN_BIT, 0)) { - dev_err(drvdata->dev, - "timeout observed when probing at offset %#x\n", - TMC_FFCR); - } - - tmc_wait_for_ready(drvdata); -} - -static void tmc_enable_hw(struct tmc_drvdata *drvdata) -{ - writel_relaxed(TMC_CTL_CAPT_EN, drvdata->base + TMC_CTL); -} - -static void tmc_disable_hw(struct tmc_drvdata *drvdata) -{ - writel_relaxed(0x0, drvdata->base + TMC_CTL); -} - -static void tmc_etb_enable_hw(struct tmc_drvdata *drvdata) -{ - /* Zero out the memory to help with debug */ - memset(drvdata->buf, 0, drvdata->size); - - CS_UNLOCK(drvdata->base); - - writel_relaxed(TMC_MODE_CIRCULAR_BUFFER, drvdata->base + TMC_MODE); - writel_relaxed(TMC_FFCR_EN_FMT | TMC_FFCR_EN_TI | - TMC_FFCR_FON_FLIN | TMC_FFCR_FON_TRIG_EVT | - TMC_FFCR_TRIGON_TRIGIN, - drvdata->base + TMC_FFCR); - - writel_relaxed(drvdata->trigger_cntr, drvdata->base + TMC_TRG); - tmc_enable_hw(drvdata); - - CS_LOCK(drvdata->base); -} - -static void tmc_etr_enable_hw(struct tmc_drvdata *drvdata) -{ - u32 axictl; - - /* Zero out the memory to help with debug */ - memset(drvdata->vaddr, 0, drvdata->size); - - CS_UNLOCK(drvdata->base); - - writel_relaxed(drvdata->size / 4, drvdata->base + TMC_RSZ); - writel_relaxed(TMC_MODE_CIRCULAR_BUFFER, drvdata->base + TMC_MODE); - - axictl = readl_relaxed(drvdata->base + TMC_AXICTL); - axictl |= TMC_AXICTL_WR_BURST_LEN; - writel_relaxed(axictl, drvdata->base + TMC_AXICTL); - axictl &= ~TMC_AXICTL_SCT_GAT_MODE; - writel_relaxed(axictl, drvdata->base + TMC_AXICTL); - axictl = (axictl & - ~(TMC_AXICTL_PROT_CTL_B0 | TMC_AXICTL_PROT_CTL_B1)) | - TMC_AXICTL_PROT_CTL_B1; - writel_relaxed(axictl, drvdata->base + TMC_AXICTL); - - writel_relaxed(drvdata->paddr, drvdata->base + TMC_DBALO); - writel_relaxed(0x0, drvdata->base + TMC_DBAHI); - writel_relaxed(TMC_FFCR_EN_FMT | TMC_FFCR_EN_TI | - TMC_FFCR_FON_FLIN | TMC_FFCR_FON_TRIG_EVT | - TMC_FFCR_TRIGON_TRIGIN, - drvdata->base + TMC_FFCR); - writel_relaxed(drvdata->trigger_cntr, drvdata->base + TMC_TRG); - tmc_enable_hw(drvdata); - - CS_LOCK(drvdata->base); -} - -static void tmc_etf_enable_hw(struct tmc_drvdata *drvdata) -{ - CS_UNLOCK(drvdata->base); - - writel_relaxed(TMC_MODE_HARDWARE_FIFO, drvdata->base + TMC_MODE); - writel_relaxed(TMC_FFCR_EN_FMT | TMC_FFCR_EN_TI, - drvdata->base + TMC_FFCR); - writel_relaxed(0x0, drvdata->base + TMC_BUFWM); - tmc_enable_hw(drvdata); - - CS_LOCK(drvdata->base); -} - -static int tmc_enable(struct tmc_drvdata *drvdata, enum tmc_mode mode) -{ - int ret; - unsigned long flags; - - ret = clk_prepare_enable(drvdata->clk); - if (ret) - return ret; - - spin_lock_irqsave(&drvdata->spinlock, flags); - if (drvdata->reading) { - spin_unlock_irqrestore(&drvdata->spinlock, flags); - clk_disable_unprepare(drvdata->clk); - return -EBUSY; - } - - if (drvdata->config_type == TMC_CONFIG_TYPE_ETB) { - tmc_etb_enable_hw(drvdata); - } else if (drvdata->config_type == TMC_CONFIG_TYPE_ETR) { - tmc_etr_enable_hw(drvdata); - } else { - if (mode == TMC_MODE_CIRCULAR_BUFFER) - tmc_etb_enable_hw(drvdata); - else - tmc_etf_enable_hw(drvdata); - } - drvdata->enable = true; - spin_unlock_irqrestore(&drvdata->spinlock, flags); - - dev_info(drvdata->dev, "TMC enabled\n"); - return 0; -} - -static int tmc_enable_sink(struct coresight_device *csdev) -{ - struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); - - return tmc_enable(drvdata, TMC_MODE_CIRCULAR_BUFFER); -} - -static int tmc_enable_link(struct coresight_device *csdev, int inport, - int outport) -{ - struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); - - return tmc_enable(drvdata, TMC_MODE_HARDWARE_FIFO); -} - -static void tmc_etb_dump_hw(struct tmc_drvdata *drvdata) -{ - enum tmc_mem_intf_width memwidth; - u8 memwords; - char *bufp; - u32 read_data; - int i; - - memwidth = BMVAL(readl_relaxed(drvdata->base + CORESIGHT_DEVID), 8, 10); - if (memwidth == TMC_MEM_INTF_WIDTH_32BITS) - memwords = 1; - else if (memwidth == TMC_MEM_INTF_WIDTH_64BITS) - memwords = 2; - else if (memwidth == TMC_MEM_INTF_WIDTH_128BITS) - memwords = 4; - else - memwords = 8; - - bufp = drvdata->buf; - while (1) { - for (i = 0; i < memwords; i++) { - read_data = readl_relaxed(drvdata->base + TMC_RRD); - if (read_data == 0xFFFFFFFF) - return; - memcpy(bufp, &read_data, 4); - bufp += 4; - } - } -} - -static void tmc_etb_disable_hw(struct tmc_drvdata *drvdata) -{ - CS_UNLOCK(drvdata->base); - - tmc_flush_and_stop(drvdata); - tmc_etb_dump_hw(drvdata); - tmc_disable_hw(drvdata); - - CS_LOCK(drvdata->base); -} - -static void tmc_etr_dump_hw(struct tmc_drvdata *drvdata) -{ - u32 rwp, val; - - rwp = readl_relaxed(drvdata->base + TMC_RWP); - val = readl_relaxed(drvdata->base + TMC_STS); - - /* How much memory do we still have */ - if (val & BIT(0)) - drvdata->buf = drvdata->vaddr + rwp - drvdata->paddr; - else - drvdata->buf = drvdata->vaddr; -} - -static void tmc_etr_disable_hw(struct tmc_drvdata *drvdata) -{ - CS_UNLOCK(drvdata->base); - - tmc_flush_and_stop(drvdata); - tmc_etr_dump_hw(drvdata); - tmc_disable_hw(drvdata); - - CS_LOCK(drvdata->base); -} - -static void tmc_etf_disable_hw(struct tmc_drvdata *drvdata) -{ - CS_UNLOCK(drvdata->base); - - tmc_flush_and_stop(drvdata); - tmc_disable_hw(drvdata); - - CS_LOCK(drvdata->base); -} - -static void tmc_disable(struct tmc_drvdata *drvdata, enum tmc_mode mode) -{ - unsigned long flags; - - spin_lock_irqsave(&drvdata->spinlock, flags); - if (drvdata->reading) - goto out; - - if (drvdata->config_type == TMC_CONFIG_TYPE_ETB) { - tmc_etb_disable_hw(drvdata); - } else if (drvdata->config_type == TMC_CONFIG_TYPE_ETR) { - tmc_etr_disable_hw(drvdata); - } else { - if (mode == TMC_MODE_CIRCULAR_BUFFER) - tmc_etb_disable_hw(drvdata); - else - tmc_etf_disable_hw(drvdata); - } -out: - drvdata->enable = false; - spin_unlock_irqrestore(&drvdata->spinlock, flags); - - clk_disable_unprepare(drvdata->clk); - - dev_info(drvdata->dev, "TMC disabled\n"); -} - -static void tmc_disable_sink(struct coresight_device *csdev) -{ - struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); - - tmc_disable(drvdata, TMC_MODE_CIRCULAR_BUFFER); -} - -static void tmc_disable_link(struct coresight_device *csdev, int inport, - int outport) -{ - struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); - - tmc_disable(drvdata, TMC_MODE_HARDWARE_FIFO); -} - -static const struct coresight_ops_sink tmc_sink_ops = { - .enable = tmc_enable_sink, - .disable = tmc_disable_sink, -}; - -static const struct coresight_ops_link tmc_link_ops = { - .enable = tmc_enable_link, - .disable = tmc_disable_link, -}; - -static const struct coresight_ops tmc_etb_cs_ops = { - .sink_ops = &tmc_sink_ops, -}; - -static const struct coresight_ops tmc_etr_cs_ops = { - .sink_ops = &tmc_sink_ops, -}; - -static const struct coresight_ops tmc_etf_cs_ops = { - .sink_ops = &tmc_sink_ops, - .link_ops = &tmc_link_ops, -}; - -static int tmc_read_prepare(struct tmc_drvdata *drvdata) -{ - int ret; - unsigned long flags; - enum tmc_mode mode; - - spin_lock_irqsave(&drvdata->spinlock, flags); - if (!drvdata->enable) - goto out; - - if (drvdata->config_type == TMC_CONFIG_TYPE_ETB) { - tmc_etb_disable_hw(drvdata); - } else if (drvdata->config_type == TMC_CONFIG_TYPE_ETR) { - tmc_etr_disable_hw(drvdata); - } else { - mode = readl_relaxed(drvdata->base + TMC_MODE); - if (mode == TMC_MODE_CIRCULAR_BUFFER) { - tmc_etb_disable_hw(drvdata); - } else { - ret = -ENODEV; - goto err; - } - } -out: - drvdata->reading = true; - spin_unlock_irqrestore(&drvdata->spinlock, flags); - - dev_info(drvdata->dev, "TMC read start\n"); - return 0; -err: - spin_unlock_irqrestore(&drvdata->spinlock, flags); - return ret; -} - -static void tmc_read_unprepare(struct tmc_drvdata *drvdata) -{ - unsigned long flags; - enum tmc_mode mode; - - spin_lock_irqsave(&drvdata->spinlock, flags); - if (!drvdata->enable) - goto out; - - if (drvdata->config_type == TMC_CONFIG_TYPE_ETB) { - tmc_etb_enable_hw(drvdata); - } else if (drvdata->config_type == TMC_CONFIG_TYPE_ETR) { - tmc_etr_enable_hw(drvdata); - } else { - mode = readl_relaxed(drvdata->base + TMC_MODE); - if (mode == TMC_MODE_CIRCULAR_BUFFER) - tmc_etb_enable_hw(drvdata); - } -out: - drvdata->reading = false; - spin_unlock_irqrestore(&drvdata->spinlock, flags); - - dev_info(drvdata->dev, "TMC read end\n"); -} - -static int tmc_open(struct inode *inode, struct file *file) -{ - struct tmc_drvdata *drvdata = container_of(file->private_data, - struct tmc_drvdata, miscdev); - int ret = 0; - - if (drvdata->read_count++) - goto out; - - ret = tmc_read_prepare(drvdata); - if (ret) - return ret; -out: - nonseekable_open(inode, file); - - dev_dbg(drvdata->dev, "%s: successfully opened\n", __func__); - return 0; -} - -static ssize_t tmc_read(struct file *file, char __user *data, size_t len, - loff_t *ppos) -{ - struct tmc_drvdata *drvdata = container_of(file->private_data, - struct tmc_drvdata, miscdev); - char *bufp = drvdata->buf + *ppos; - - if (*ppos + len > drvdata->size) - len = drvdata->size - *ppos; - - if (drvdata->config_type == TMC_CONFIG_TYPE_ETR) { - if (bufp == (char *)(drvdata->vaddr + drvdata->size)) - bufp = drvdata->vaddr; - else if (bufp > (char *)(drvdata->vaddr + drvdata->size)) - bufp -= drvdata->size; - if ((bufp + len) > (char *)(drvdata->vaddr + drvdata->size)) - len = (char *)(drvdata->vaddr + drvdata->size) - bufp; - } - - if (copy_to_user(data, bufp, len)) { - dev_dbg(drvdata->dev, "%s: copy_to_user failed\n", __func__); - return -EFAULT; - } - - *ppos += len; - - dev_dbg(drvdata->dev, "%s: %zu bytes copied, %d bytes left\n", - __func__, len, (int)(drvdata->size - *ppos)); - return len; -} - -static int tmc_release(struct inode *inode, struct file *file) -{ - struct tmc_drvdata *drvdata = container_of(file->private_data, - struct tmc_drvdata, miscdev); - - if (--drvdata->read_count) { - if (drvdata->read_count < 0) { - dev_err(drvdata->dev, "mismatched close\n"); - drvdata->read_count = 0; - } - goto out; - } - - tmc_read_unprepare(drvdata); -out: - dev_dbg(drvdata->dev, "%s: released\n", __func__); - return 0; -} - -static const struct file_operations tmc_fops = { - .owner = THIS_MODULE, - .open = tmc_open, - .read = tmc_read, - .release = tmc_release, - .llseek = no_llseek, -}; - -static ssize_t status_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - int ret; - unsigned long flags; - u32 tmc_rsz, tmc_sts, tmc_rrp, tmc_rwp, tmc_trg; - u32 tmc_ctl, tmc_ffsr, tmc_ffcr, tmc_mode, tmc_pscr; - u32 devid; - struct tmc_drvdata *drvdata = dev_get_drvdata(dev->parent); - - ret = clk_prepare_enable(drvdata->clk); - if (ret) - goto out; - - spin_lock_irqsave(&drvdata->spinlock, flags); - CS_UNLOCK(drvdata->base); - - tmc_rsz = readl_relaxed(drvdata->base + TMC_RSZ); - tmc_sts = readl_relaxed(drvdata->base + TMC_STS); - tmc_rrp = readl_relaxed(drvdata->base + TMC_RRP); - tmc_rwp = readl_relaxed(drvdata->base + TMC_RWP); - tmc_trg = readl_relaxed(drvdata->base + TMC_TRG); - tmc_ctl = readl_relaxed(drvdata->base + TMC_CTL); - tmc_ffsr = readl_relaxed(drvdata->base + TMC_FFSR); - tmc_ffcr = readl_relaxed(drvdata->base + TMC_FFCR); - tmc_mode = readl_relaxed(drvdata->base + TMC_MODE); - tmc_pscr = readl_relaxed(drvdata->base + TMC_PSCR); - devid = readl_relaxed(drvdata->base + CORESIGHT_DEVID); - - CS_LOCK(drvdata->base); - spin_unlock_irqrestore(&drvdata->spinlock, flags); - - clk_disable_unprepare(drvdata->clk); - - return sprintf(buf, - "Depth:\t\t0x%x\n" - "Status:\t\t0x%x\n" - "RAM read ptr:\t0x%x\n" - "RAM wrt ptr:\t0x%x\n" - "Trigger cnt:\t0x%x\n" - "Control:\t0x%x\n" - "Flush status:\t0x%x\n" - "Flush ctrl:\t0x%x\n" - "Mode:\t\t0x%x\n" - "PSRC:\t\t0x%x\n" - "DEVID:\t\t0x%x\n", - tmc_rsz, tmc_sts, tmc_rrp, tmc_rwp, tmc_trg, - tmc_ctl, tmc_ffsr, tmc_ffcr, tmc_mode, tmc_pscr, devid); -out: - return -EINVAL; -} -static DEVICE_ATTR_RO(status); - -static ssize_t trigger_cntr_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct tmc_drvdata *drvdata = dev_get_drvdata(dev->parent); - unsigned long val = drvdata->trigger_cntr; - - return sprintf(buf, "%#lx\n", val); -} - -static ssize_t trigger_cntr_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t size) -{ - int ret; - unsigned long val; - struct tmc_drvdata *drvdata = dev_get_drvdata(dev->parent); - - ret = kstrtoul(buf, 16, &val); - if (ret) - return ret; - - drvdata->trigger_cntr = val; - return size; -} -static DEVICE_ATTR_RW(trigger_cntr); - -static struct attribute *coresight_etb_attrs[] = { - &dev_attr_trigger_cntr.attr, - &dev_attr_status.attr, - NULL, -}; -ATTRIBUTE_GROUPS(coresight_etb); - -static struct attribute *coresight_etr_attrs[] = { - &dev_attr_trigger_cntr.attr, - &dev_attr_status.attr, - NULL, -}; -ATTRIBUTE_GROUPS(coresight_etr); - -static struct attribute *coresight_etf_attrs[] = { - &dev_attr_trigger_cntr.attr, - &dev_attr_status.attr, - NULL, -}; -ATTRIBUTE_GROUPS(coresight_etf); - -static int tmc_probe(struct amba_device *adev, const struct amba_id *id) -{ - int ret = 0; - u32 devid; - void __iomem *base; - struct device *dev = &adev->dev; - struct coresight_platform_data *pdata = NULL; - struct tmc_drvdata *drvdata; - struct resource *res = &adev->res; - struct coresight_desc *desc; - struct device_node *np = adev->dev.of_node; - - if (np) { - pdata = of_get_coresight_platform_data(dev, np); - if (IS_ERR(pdata)) - return PTR_ERR(pdata); - adev->dev.platform_data = pdata; - } - - drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); - if (!drvdata) - return -ENOMEM; - - drvdata->dev = &adev->dev; - dev_set_drvdata(dev, drvdata); - - /* Validity for the resource is already checked by the AMBA core */ - base = devm_ioremap_resource(dev, res); - if (IS_ERR(base)) - return PTR_ERR(base); - - drvdata->base = base; - - spin_lock_init(&drvdata->spinlock); - - drvdata->clk = adev->pclk; - ret = clk_prepare_enable(drvdata->clk); - if (ret) - return ret; - - devid = readl_relaxed(drvdata->base + CORESIGHT_DEVID); - drvdata->config_type = BMVAL(devid, 6, 7); - - if (drvdata->config_type == TMC_CONFIG_TYPE_ETR) { - if (np) - ret = of_property_read_u32(np, - "arm,buffer-size", - &drvdata->size); - if (ret) - drvdata->size = SZ_1M; - } else { - drvdata->size = readl_relaxed(drvdata->base + TMC_RSZ) * 4; - } - - clk_disable_unprepare(drvdata->clk); - - if (drvdata->config_type == TMC_CONFIG_TYPE_ETR) { - drvdata->vaddr = dma_alloc_coherent(dev, drvdata->size, - &drvdata->paddr, GFP_KERNEL); - if (!drvdata->vaddr) - return -ENOMEM; - - memset(drvdata->vaddr, 0, drvdata->size); - drvdata->buf = drvdata->vaddr; - } else { - drvdata->buf = devm_kzalloc(dev, drvdata->size, GFP_KERNEL); - if (!drvdata->buf) - return -ENOMEM; - } - - desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL); - if (!desc) { - ret = -ENOMEM; - goto err_devm_kzalloc; - } - - desc->pdata = pdata; - desc->dev = dev; - desc->subtype.sink_subtype = CORESIGHT_DEV_SUBTYPE_SINK_BUFFER; - - if (drvdata->config_type == TMC_CONFIG_TYPE_ETB) { - desc->type = CORESIGHT_DEV_TYPE_SINK; - desc->ops = &tmc_etb_cs_ops; - desc->groups = coresight_etb_groups; - } else if (drvdata->config_type == TMC_CONFIG_TYPE_ETR) { - desc->type = CORESIGHT_DEV_TYPE_SINK; - desc->ops = &tmc_etr_cs_ops; - desc->groups = coresight_etr_groups; - } else { - desc->type = CORESIGHT_DEV_TYPE_LINKSINK; - desc->subtype.link_subtype = CORESIGHT_DEV_SUBTYPE_LINK_FIFO; - desc->ops = &tmc_etf_cs_ops; - desc->groups = coresight_etf_groups; - } - - drvdata->csdev = coresight_register(desc); - if (IS_ERR(drvdata->csdev)) { - ret = PTR_ERR(drvdata->csdev); - goto err_devm_kzalloc; - } - - drvdata->miscdev.name = pdata->name; - drvdata->miscdev.minor = MISC_DYNAMIC_MINOR; - drvdata->miscdev.fops = &tmc_fops; - ret = misc_register(&drvdata->miscdev); - if (ret) - goto err_misc_register; - - dev_info(dev, "TMC initialized\n"); - return 0; - -err_misc_register: - coresight_unregister(drvdata->csdev); -err_devm_kzalloc: - if (drvdata->config_type == TMC_CONFIG_TYPE_ETR) - dma_free_coherent(dev, drvdata->size, - &drvdata->paddr, GFP_KERNEL); - 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, - .mask = 0x0003ffff, - }, - { 0, 0}, -}; - -static struct amba_driver tmc_driver = { - .drv = { - .name = "coresight-tmc", - .owner = THIS_MODULE, - }, - .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"); diff --git a/drivers/coresight/coresight-tpiu.c b/drivers/coresight/coresight-tpiu.c deleted file mode 100644 index 3b33af2..0000000 --- a/drivers/coresight/coresight-tpiu.c +++ /dev/null @@ -1,207 +0,0 @@ -/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 and - * only version 2 as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "coresight-priv.h" - -#define TPIU_SUPP_PORTSZ 0x000 -#define TPIU_CURR_PORTSZ 0x004 -#define TPIU_SUPP_TRIGMODES 0x100 -#define TPIU_TRIG_CNTRVAL 0x104 -#define TPIU_TRIG_MULT 0x108 -#define TPIU_SUPP_TESTPATM 0x200 -#define TPIU_CURR_TESTPATM 0x204 -#define TPIU_TEST_PATREPCNTR 0x208 -#define TPIU_FFSR 0x300 -#define TPIU_FFCR 0x304 -#define TPIU_FSYNC_CNTR 0x308 -#define TPIU_EXTCTL_INPORT 0x400 -#define TPIU_EXTCTL_OUTPORT 0x404 -#define TPIU_ITTRFLINACK 0xee4 -#define TPIU_ITTRFLIN 0xee8 -#define TPIU_ITATBDATA0 0xeec -#define TPIU_ITATBCTR2 0xef0 -#define TPIU_ITATBCTR1 0xef4 -#define TPIU_ITATBCTR0 0xef8 - -/** register definition **/ -/* FFCR - 0x304 */ -#define FFCR_FON_MAN BIT(6) - -/** - * @base: memory mapped base address for this component. - * @dev: the device entity associated to this component. - * @csdev: component vitals needed by the framework. - * @clk: the clock this component is associated to. - */ -struct tpiu_drvdata { - void __iomem *base; - struct device *dev; - struct coresight_device *csdev; - struct clk *clk; -}; - -static void tpiu_enable_hw(struct tpiu_drvdata *drvdata) -{ - CS_UNLOCK(drvdata->base); - - /* TODO: fill this up */ - - CS_LOCK(drvdata->base); -} - -static int tpiu_enable(struct coresight_device *csdev) -{ - struct tpiu_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); - int ret; - - ret = clk_prepare_enable(drvdata->clk); - if (ret) - return ret; - - tpiu_enable_hw(drvdata); - - dev_info(drvdata->dev, "TPIU enabled\n"); - return 0; -} - -static void tpiu_disable_hw(struct tpiu_drvdata *drvdata) -{ - CS_UNLOCK(drvdata->base); - - /* Clear formatter controle reg. */ - writel_relaxed(0x0, drvdata->base + TPIU_FFCR); - /* Generate manual flush */ - writel_relaxed(FFCR_FON_MAN, drvdata->base + TPIU_FFCR); - - CS_LOCK(drvdata->base); -} - -static void tpiu_disable(struct coresight_device *csdev) -{ - struct tpiu_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); - - tpiu_disable_hw(drvdata); - - clk_disable_unprepare(drvdata->clk); - - dev_info(drvdata->dev, "TPIU disabled\n"); -} - -static const struct coresight_ops_sink tpiu_sink_ops = { - .enable = tpiu_enable, - .disable = tpiu_disable, -}; - -static const struct coresight_ops tpiu_cs_ops = { - .sink_ops = &tpiu_sink_ops, -}; - -static int tpiu_probe(struct amba_device *adev, const struct amba_id *id) -{ - int ret; - void __iomem *base; - struct device *dev = &adev->dev; - struct coresight_platform_data *pdata = NULL; - struct tpiu_drvdata *drvdata; - struct resource *res = &adev->res; - struct coresight_desc *desc; - struct device_node *np = adev->dev.of_node; - - if (np) { - pdata = of_get_coresight_platform_data(dev, np); - if (IS_ERR(pdata)) - return PTR_ERR(pdata); - adev->dev.platform_data = pdata; - } - - drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); - if (!drvdata) - return -ENOMEM; - - drvdata->dev = &adev->dev; - dev_set_drvdata(dev, drvdata); - - /* Validity for the resource is already checked by the AMBA core */ - base = devm_ioremap_resource(dev, res); - if (IS_ERR(base)) - return PTR_ERR(base); - - drvdata->base = base; - - drvdata->clk = adev->pclk; - ret = clk_prepare_enable(drvdata->clk); - if (ret) - return ret; - - /* Disable tpiu to support older devices */ - tpiu_disable_hw(drvdata); - - clk_disable_unprepare(drvdata->clk); - - desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL); - if (!desc) - return -ENOMEM; - - desc->type = CORESIGHT_DEV_TYPE_SINK; - desc->subtype.sink_subtype = CORESIGHT_DEV_SUBTYPE_SINK_PORT; - desc->ops = &tpiu_cs_ops; - desc->pdata = pdata; - desc->dev = dev; - drvdata->csdev = coresight_register(desc); - if (IS_ERR(drvdata->csdev)) - return PTR_ERR(drvdata->csdev); - - dev_info(dev, "TPIU initialized\n"); - return 0; -} - -static int tpiu_remove(struct amba_device *adev) -{ - struct tpiu_drvdata *drvdata = amba_get_drvdata(adev); - - coresight_unregister(drvdata->csdev); - return 0; -} - -static struct amba_id tpiu_ids[] = { - { - .id = 0x0003b912, - .mask = 0x0003ffff, - }, - { 0, 0}, -}; - -static struct amba_driver tpiu_driver = { - .drv = { - .name = "coresight-tpiu", - .owner = THIS_MODULE, - }, - .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"); diff --git a/drivers/coresight/coresight.c b/drivers/coresight/coresight.c deleted file mode 100644 index 894531d..0000000 --- a/drivers/coresight/coresight.c +++ /dev/null @@ -1,720 +0,0 @@ -/* Copyright (c) 2012, The Linux Foundation. All rights reserved. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 and - * only version 2 as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "coresight-priv.h" - -static DEFINE_MUTEX(coresight_mutex); - -static int coresight_id_match(struct device *dev, void *data) -{ - int trace_id, i_trace_id; - struct coresight_device *csdev, *i_csdev; - - csdev = data; - i_csdev = to_coresight_device(dev); - - /* - * No need to care about oneself and components that are not - * sources or not enabled - */ - if (i_csdev == csdev || !i_csdev->enable || - i_csdev->type != CORESIGHT_DEV_TYPE_SOURCE) - return 0; - - /* Get the source ID for both compoment */ - trace_id = source_ops(csdev)->trace_id(csdev); - i_trace_id = source_ops(i_csdev)->trace_id(i_csdev); - - /* All you need is one */ - if (trace_id == i_trace_id) - return 1; - - return 0; -} - -static int coresight_source_is_unique(struct coresight_device *csdev) -{ - int trace_id = source_ops(csdev)->trace_id(csdev); - - /* this shouldn't happen */ - if (trace_id < 0) - return 0; - - return !bus_for_each_dev(&coresight_bustype, NULL, - csdev, coresight_id_match); -} - -static int coresight_find_link_inport(struct coresight_device *csdev) -{ - 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) - return conn->child_port; - } - - dev_err(&csdev->dev, "couldn't find inport, parent: %s, child: %s\n", - dev_name(&parent->dev), dev_name(&csdev->dev)); - - return 0; -} - -static int coresight_find_link_outport(struct coresight_device *csdev) -{ - 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) - return conn->outport; - } - - dev_err(&csdev->dev, "couldn't find outport, parent: %s, child: %s\n", - dev_name(&csdev->dev), dev_name(&child->dev)); - - return 0; -} - -static int coresight_enable_sink(struct coresight_device *csdev) -{ - int ret; - - if (!csdev->enable) { - if (sink_ops(csdev)->enable) { - ret = sink_ops(csdev)->enable(csdev); - if (ret) - return ret; - } - csdev->enable = true; - } - - atomic_inc(csdev->refcnt); - - return 0; -} - -static void coresight_disable_sink(struct coresight_device *csdev) -{ - if (atomic_dec_return(csdev->refcnt) == 0) { - if (sink_ops(csdev)->disable) { - sink_ops(csdev)->disable(csdev); - csdev->enable = false; - } - } -} - -static int coresight_enable_link(struct coresight_device *csdev) -{ - int ret; - int link_subtype; - int refport, inport, outport; - - inport = coresight_find_link_inport(csdev); - outport = coresight_find_link_outport(csdev); - link_subtype = csdev->subtype.link_subtype; - - if (link_subtype == CORESIGHT_DEV_SUBTYPE_LINK_MERG) - refport = inport; - else if (link_subtype == CORESIGHT_DEV_SUBTYPE_LINK_SPLIT) - refport = outport; - else - refport = 0; - - if (atomic_inc_return(&csdev->refcnt[refport]) == 1) { - if (link_ops(csdev)->enable) { - ret = link_ops(csdev)->enable(csdev, inport, outport); - if (ret) - return ret; - } - } - - csdev->enable = true; - - return 0; -} - -static void coresight_disable_link(struct coresight_device *csdev) -{ - int i, nr_conns; - int link_subtype; - int refport, inport, outport; - - inport = coresight_find_link_inport(csdev); - outport = coresight_find_link_outport(csdev); - link_subtype = csdev->subtype.link_subtype; - - if (link_subtype == CORESIGHT_DEV_SUBTYPE_LINK_MERG) { - refport = inport; - nr_conns = csdev->nr_inport; - } else if (link_subtype == CORESIGHT_DEV_SUBTYPE_LINK_SPLIT) { - refport = outport; - nr_conns = csdev->nr_outport; - } else { - refport = 0; - nr_conns = 1; - } - - if (atomic_dec_return(&csdev->refcnt[refport]) == 0) { - if (link_ops(csdev)->disable) - link_ops(csdev)->disable(csdev, inport, outport); - } - - for (i = 0; i < nr_conns; i++) - if (atomic_read(&csdev->refcnt[i]) != 0) - return; - - csdev->enable = false; -} - -static int coresight_enable_source(struct coresight_device *csdev) -{ - int ret; - - if (!coresight_source_is_unique(csdev)) { - dev_warn(&csdev->dev, "traceID %d not unique\n", - source_ops(csdev)->trace_id(csdev)); - return -EINVAL; - } - - if (!csdev->enable) { - if (source_ops(csdev)->enable) { - ret = source_ops(csdev)->enable(csdev); - if (ret) - return ret; - } - csdev->enable = true; - } - - atomic_inc(csdev->refcnt); - - return 0; -} - -static void coresight_disable_source(struct coresight_device *csdev) -{ - if (atomic_dec_return(csdev->refcnt) == 0) { - if (source_ops(csdev)->disable) { - source_ops(csdev)->disable(csdev); - csdev->enable = false; - } - } -} - -static int coresight_enable_path(struct list_head *path) -{ - int ret = 0; - struct coresight_device *cd; - - 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) - 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); - } - } - - return ret; -} - -static int coresight_disable_path(struct list_head *path) -{ - struct coresight_device *cd; - - 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); - } - } - - return 0; -} - -static int coresight_build_paths(struct coresight_device *csdev, - struct list_head *path, - bool enable) -{ - int i, ret = -EINVAL; - struct coresight_connection *conn; - - list_add(&csdev->path_link, path); - - 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; - } - } - - if (list_first_entry(path, struct coresight_device, path_link) != csdev) - dev_err(&csdev->dev, "wrong device in %s\n", __func__); - - list_del(&csdev->path_link); - - return ret; -} - -int coresight_enable(struct coresight_device *csdev) -{ - int ret = 0; - LIST_HEAD(path); - - mutex_lock(&coresight_mutex); - if (csdev->type != CORESIGHT_DEV_TYPE_SOURCE) { - ret = -EINVAL; - dev_err(&csdev->dev, "wrong device type in %s\n", __func__); - goto out; - } - if (csdev->enable) - goto out; - - if (coresight_build_paths(csdev, &path, true)) { - dev_err(&csdev->dev, "building path(s) failed\n"); - goto out; - } - - if (coresight_enable_source(csdev)) - dev_err(&csdev->dev, "source enable failed\n"); -out: - mutex_unlock(&coresight_mutex); - return ret; -} -EXPORT_SYMBOL_GPL(coresight_enable); - -void coresight_disable(struct coresight_device *csdev) -{ - LIST_HEAD(path); - - mutex_lock(&coresight_mutex); - if (csdev->type != CORESIGHT_DEV_TYPE_SOURCE) { - dev_err(&csdev->dev, "wrong device type in %s\n", __func__); - goto out; - } - if (!csdev->enable) - goto out; - - coresight_disable_source(csdev); - if (coresight_build_paths(csdev, &path, false)) - dev_err(&csdev->dev, "releasing path(s) failed\n"); - -out: - mutex_unlock(&coresight_mutex); -} -EXPORT_SYMBOL_GPL(coresight_disable); - -static ssize_t enable_sink_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct coresight_device *csdev = to_coresight_device(dev); - - return scnprintf(buf, PAGE_SIZE, "%u\n", (unsigned)csdev->activated); -} - -static ssize_t enable_sink_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t size) -{ - int ret; - unsigned long val; - struct coresight_device *csdev = to_coresight_device(dev); - - ret = kstrtoul(buf, 10, &val); - if (ret) - return ret; - - if (val) - csdev->activated = true; - else - csdev->activated = false; - - return size; - -} -static DEVICE_ATTR_RW(enable_sink); - -static ssize_t enable_source_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct coresight_device *csdev = to_coresight_device(dev); - - return scnprintf(buf, PAGE_SIZE, "%u\n", (unsigned)csdev->enable); -} - -static ssize_t enable_source_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t size) -{ - int ret = 0; - unsigned long val; - struct coresight_device *csdev = to_coresight_device(dev); - - ret = kstrtoul(buf, 10, &val); - if (ret) - return ret; - - if (val) { - ret = coresight_enable(csdev); - if (ret) - return ret; - } else { - coresight_disable(csdev); - } - - return size; -} -static DEVICE_ATTR_RW(enable_source); - -static struct attribute *coresight_sink_attrs[] = { - &dev_attr_enable_sink.attr, - NULL, -}; -ATTRIBUTE_GROUPS(coresight_sink); - -static struct attribute *coresight_source_attrs[] = { - &dev_attr_enable_source.attr, - NULL, -}; -ATTRIBUTE_GROUPS(coresight_source); - -static struct device_type coresight_dev_type[] = { - { - .name = "none", - }, - { - .name = "sink", - .groups = coresight_sink_groups, - }, - { - .name = "link", - }, - { - .name = "linksink", - .groups = coresight_sink_groups, - }, - { - .name = "source", - .groups = coresight_source_groups, - }, -}; - -static void coresight_device_release(struct device *dev) -{ - struct coresight_device *csdev = to_coresight_device(dev); - - kfree(csdev); -} - -static int coresight_orphan_match(struct device *dev, void *data) -{ - int i; - bool still_orphan = false; - struct coresight_device *csdev, *i_csdev; - struct coresight_connection *conn; - - csdev = data; - i_csdev = to_coresight_device(dev); - - /* No need to check oneself */ - if (csdev == i_csdev) - return 0; - - /* Move on to another component if no connection is orphan */ - if (!i_csdev->orphan) - return 0; - /* - * Circle throuch all the connection of that component. If we find - * an orphan connection whose name matches @csdev, link it. - */ - for (i = 0; i < i_csdev->nr_outport; i++) { - conn = &i_csdev->conns[i]; - - /* We have found at least one orphan connection */ - if (conn->child_dev == NULL) { - /* Does it match this newly added device? */ - if (!strcmp(dev_name(&csdev->dev), conn->child_name)) { - conn->child_dev = csdev; - } else { - /* This component still has an orphan */ - still_orphan = true; - } - } - } - - i_csdev->orphan = still_orphan; - - /* - * Returning '0' ensures that all known component on the - * bus will be checked. - */ - return 0; -} - -static void coresight_fixup_orphan_conns(struct coresight_device *csdev) -{ - /* - * No need to check for a return value as orphan connection(s) - * are hooked-up with each newly added component. - */ - bus_for_each_dev(&coresight_bustype, NULL, - csdev, coresight_orphan_match); -} - - -static int coresight_name_match(struct device *dev, void *data) -{ - char *to_match; - struct coresight_device *i_csdev; - - to_match = data; - i_csdev = to_coresight_device(dev); - - if (!strcmp(to_match, dev_name(&i_csdev->dev))) - return 1; - - return 0; -} - -static void coresight_fixup_device_conns(struct coresight_device *csdev) -{ - int i; - struct device *dev = NULL; - struct coresight_connection *conn; - - for (i = 0; i < csdev->nr_outport; i++) { - conn = &csdev->conns[i]; - dev = bus_find_device(&coresight_bustype, NULL, - (void *)conn->child_name, - coresight_name_match); - - if (dev) { - conn->child_dev = to_coresight_device(dev); - } else { - csdev->orphan = true; - conn->child_dev = NULL; - } - } -} - -/** - * coresight_timeout - loop until a bit has changed to a specific state. - * @addr: base address of the area of interest. - * @offset: address of a register, starting from @addr. - * @position: the position of the bit of interest. - * @value: the value the bit should have. - * - * Return: 0 as soon as the bit has taken the desired state or -EAGAIN if - * TIMEOUT_US has elapsed, which ever happens first. - */ - -int coresight_timeout(void __iomem *addr, u32 offset, int position, int value) -{ - int i; - u32 val; - - for (i = TIMEOUT_US; i > 0; i--) { - val = __raw_readl(addr + offset); - /* waiting on the bit to go from 0 to 1 */ - if (value) { - if (val & BIT(position)) - return 0; - /* waiting on the bit to go from 1 to 0 */ - } else { - if (!(val & BIT(position))) - return 0; - } - - /* - * Delay is arbitrary - the specification doesn't say how long - * we are expected to wait. Extra check required to make sure - * we don't wait needlessly on the last iteration. - */ - if (i - 1) - udelay(1); - } - - return -EAGAIN; -} - -struct bus_type coresight_bustype = { - .name = "coresight", -}; - -static int __init coresight_init(void) -{ - return bus_register(&coresight_bustype); -} -postcore_initcall(coresight_init); - -struct coresight_device *coresight_register(struct coresight_desc *desc) -{ - int i; - int ret; - int link_subtype; - int nr_refcnts = 1; - atomic_t *refcnts = NULL; - struct coresight_device *csdev; - struct coresight_connection *conns; - - csdev = kzalloc(sizeof(*csdev), GFP_KERNEL); - if (!csdev) { - ret = -ENOMEM; - goto err_kzalloc_csdev; - } - - if (desc->type == CORESIGHT_DEV_TYPE_LINK || - desc->type == CORESIGHT_DEV_TYPE_LINKSINK) { - link_subtype = desc->subtype.link_subtype; - - if (link_subtype == CORESIGHT_DEV_SUBTYPE_LINK_MERG) - nr_refcnts = desc->pdata->nr_inport; - else if (link_subtype == CORESIGHT_DEV_SUBTYPE_LINK_SPLIT) - nr_refcnts = desc->pdata->nr_outport; - } - - refcnts = kcalloc(nr_refcnts, sizeof(*refcnts), GFP_KERNEL); - if (!refcnts) { - ret = -ENOMEM; - goto err_kzalloc_refcnts; - } - - csdev->refcnt = refcnts; - - csdev->nr_inport = desc->pdata->nr_inport; - csdev->nr_outport = desc->pdata->nr_outport; - conns = kcalloc(csdev->nr_outport, sizeof(*conns), GFP_KERNEL); - if (!conns) { - ret = -ENOMEM; - goto err_kzalloc_conns; - } - - for (i = 0; i < csdev->nr_outport; i++) { - conns[i].outport = desc->pdata->outports[i]; - conns[i].child_name = desc->pdata->child_names[i]; - conns[i].child_port = desc->pdata->child_ports[i]; - } - - csdev->conns = conns; - - csdev->type = desc->type; - csdev->subtype = desc->subtype; - csdev->ops = desc->ops; - csdev->orphan = false; - - csdev->dev.type = &coresight_dev_type[desc->type]; - csdev->dev.groups = desc->groups; - csdev->dev.parent = desc->dev; - csdev->dev.release = coresight_device_release; - csdev->dev.bus = &coresight_bustype; - dev_set_name(&csdev->dev, "%s", desc->pdata->name); - - ret = device_register(&csdev->dev); - if (ret) - goto err_device_register; - - mutex_lock(&coresight_mutex); - - coresight_fixup_device_conns(csdev); - coresight_fixup_orphan_conns(csdev); - - mutex_unlock(&coresight_mutex); - - return csdev; - -err_device_register: - kfree(conns); -err_kzalloc_conns: - kfree(refcnts); -err_kzalloc_refcnts: - kfree(csdev); -err_kzalloc_csdev: - return ERR_PTR(ret); -} -EXPORT_SYMBOL_GPL(coresight_register); - -void coresight_unregister(struct coresight_device *csdev) -{ - mutex_lock(&coresight_mutex); - - kfree(csdev->conns); - device_unregister(&csdev->dev); - - mutex_unlock(&coresight_mutex); -} -EXPORT_SYMBOL_GPL(coresight_unregister); - -MODULE_LICENSE("GPL v2"); diff --git a/drivers/coresight/of_coresight.c b/drivers/coresight/of_coresight.c deleted file mode 100644 index f3cc8e9..0000000 --- a/drivers/coresight/of_coresight.c +++ /dev/null @@ -1,200 +0,0 @@ -/* Copyright (c) 2012, The Linux Foundation. All rights reserved. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 and - * only version 2 as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -static int of_dev_node_match(struct device *dev, void *data) -{ - return dev->of_node == data; -} - -static struct device * -of_coresight_get_endpoint_device(struct device_node *endpoint) -{ - struct device *dev = NULL; - - /* - * If we have a non-configuable replicator, it will be found on the - * platform bus. - */ - dev = bus_find_device(&platform_bus_type, NULL, - endpoint, of_dev_node_match); - if (dev) - return dev; - - /* - * We have a configurable component - circle through the AMBA bus - * looking for the device that matches the endpoint node. - */ - return bus_find_device(&amba_bustype, NULL, - endpoint, of_dev_node_match); -} - -static struct device_node *of_get_coresight_endpoint( - const struct device_node *parent, struct device_node *prev) -{ - struct device_node *node = of_graph_get_next_endpoint(parent, prev); - - of_node_put(prev); - return node; -} - -static void of_coresight_get_ports(struct device_node *node, - int *nr_inport, int *nr_outport) -{ - struct device_node *ep = NULL; - int in = 0, out = 0; - - do { - ep = of_get_coresight_endpoint(node, ep); - if (!ep) - break; - - if (of_property_read_bool(ep, "slave-mode")) - in++; - else - out++; - - } while (ep); - - *nr_inport = in; - *nr_outport = out; -} - -static int of_coresight_alloc_memory(struct device *dev, - struct coresight_platform_data *pdata) -{ - /* List of output port on this component */ - pdata->outports = devm_kzalloc(dev, pdata->nr_outport * - sizeof(*pdata->outports), - GFP_KERNEL); - if (!pdata->outports) - return -ENOMEM; - - /* Children connected to this component via @outports */ - pdata->child_names = devm_kzalloc(dev, pdata->nr_outport * - sizeof(*pdata->child_names), - GFP_KERNEL); - if (!pdata->child_names) - return -ENOMEM; - - /* Port number on the child this component is connected to */ - pdata->child_ports = devm_kzalloc(dev, pdata->nr_outport * - sizeof(*pdata->child_ports), - GFP_KERNEL); - if (!pdata->child_ports) - return -ENOMEM; - - return 0; -} - -struct coresight_platform_data *of_get_coresight_platform_data( - struct device *dev, struct device_node *node) -{ - int i = 0, ret = 0, cpu; - struct coresight_platform_data *pdata; - struct of_endpoint endpoint, rendpoint; - struct device *rdev; - struct device_node *dn; - struct device_node *ep = NULL; - struct device_node *rparent = NULL; - struct device_node *rport = NULL; - - pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); - if (!pdata) - return ERR_PTR(-ENOMEM); - - /* Use device name as sysfs handle */ - pdata->name = dev_name(dev); - - /* Get the number of input and output port for this component */ - of_coresight_get_ports(node, &pdata->nr_inport, &pdata->nr_outport); - - if (pdata->nr_outport) { - ret = of_coresight_alloc_memory(dev, pdata); - if (ret) - return ERR_PTR(ret); - - /* Iterate through each port to discover topology */ - do { - /* Get a handle on a port */ - ep = of_get_coresight_endpoint(node, ep); - if (!ep) - break; - - /* - * No need to deal with input ports, processing for as - * processing for output ports will deal with them. - */ - if (of_find_property(ep, "slave-mode", NULL)) - continue; - - /* Get a handle on the local endpoint */ - ret = of_graph_parse_endpoint(ep, &endpoint); - - if (ret) - continue; - - /* The local out port number */ - pdata->outports[i] = endpoint.id; - - /* - * Get a handle on the remote port and parent - * attached to it. - */ - rparent = of_graph_get_remote_port_parent(ep); - rport = of_graph_get_remote_port(ep); - - if (!rparent || !rport) - continue; - - if (of_graph_parse_endpoint(rport, &rendpoint)) - continue; - - rdev = of_coresight_get_endpoint_device(rparent); - if (!rdev) - continue; - - pdata->child_names[i] = dev_name(rdev); - pdata->child_ports[i] = rendpoint.id; - - i++; - } while (ep); - } - - /* Affinity defaults to CPU0 */ - pdata->cpu = 0; - dn = of_parse_phandle(node, "cpu", 0); - for (cpu = 0; dn && cpu < nr_cpu_ids; cpu++) { - if (dn == of_get_cpu_node(cpu, NULL)) { - pdata->cpu = cpu; - break; - } - } - - return pdata; -} -EXPORT_SYMBOL_GPL(of_get_coresight_platform_data); diff --git a/drivers/hwtracing/coresight/Kconfig b/drivers/hwtracing/coresight/Kconfig new file mode 100644 index 0000000..fc1f1ae --- /dev/null +++ b/drivers/hwtracing/coresight/Kconfig @@ -0,0 +1,61 @@ +# +# Coresight configuration +# +menuconfig CORESIGHT + bool "CoreSight Tracing Support" + select ARM_AMBA + help + This framework provides a kernel interface for the CoreSight debug + and trace drivers to register themselves with. It's intended to build + a topological view of the CoreSight components based on a DT + specification and configure the right serie of components when a + trace source gets enabled. + +if CORESIGHT +config CORESIGHT_LINKS_AND_SINKS + bool "CoreSight Link and Sink drivers" + help + This enables support for CoreSight link and sink drivers that are + responsible for transporting and collecting the trace data + respectively. Link and sinks are dynamically aggregated with a trace + entity at run time to form a complete trace path. + +config CORESIGHT_LINK_AND_SINK_TMC + bool "Coresight generic TMC driver" + depends on CORESIGHT_LINKS_AND_SINKS + help + This enables support for the Trace Memory Controller driver. + Depending on its configuration the device can act as a link (embedded + trace router - ETR) or sink (embedded trace FIFO). The driver + complies with the generic implementation of the component without + special enhancement or added features. + +config CORESIGHT_SINK_TPIU + bool "Coresight generic TPIU driver" + depends on CORESIGHT_LINKS_AND_SINKS + help + This enables support for the Trace Port Interface Unit driver, + responsible for bridging the gap between the on-chip coresight + components and a trace for bridging the gap between the on-chip + coresight components and a trace port collection engine, typically + connected to an external host for use case capturing more traces than + the on-board coresight memory can handle. + +config CORESIGHT_SINK_ETBV10 + bool "Coresight ETBv1.0 driver" + depends on CORESIGHT_LINKS_AND_SINKS + help + This enables support for the Embedded Trace Buffer version 1.0 driver + that complies with the generic implementation of the component without + special enhancement or added features. + +config CORESIGHT_SOURCE_ETM3X + bool "CoreSight Embedded Trace Macrocell 3.x driver" + depends on !ARM64 + select CORESIGHT_LINKS_AND_SINKS + help + This driver provides support for processor ETM3.x and PTM1.x modules, + which allows tracing the instructions that a processor is executing + This is primarily useful for instruction level tracing. Depending + the ETM version data tracing may also be available. +endif diff --git a/drivers/hwtracing/coresight/Makefile b/drivers/hwtracing/coresight/Makefile new file mode 100644 index 0000000..4b4bec8 --- /dev/null +++ b/drivers/hwtracing/coresight/Makefile @@ -0,0 +1,11 @@ +# +# Makefile for CoreSight drivers. +# +obj-$(CONFIG_CORESIGHT) += coresight.o +obj-$(CONFIG_OF) += of_coresight.o +obj-$(CONFIG_CORESIGHT_LINK_AND_SINK_TMC) += coresight-tmc.o +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 diff --git a/drivers/hwtracing/coresight/coresight-etb10.c b/drivers/hwtracing/coresight/coresight-etb10.c new file mode 100644 index 0000000..4004986 --- /dev/null +++ b/drivers/hwtracing/coresight/coresight-etb10.c @@ -0,0 +1,527 @@ +/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "coresight-priv.h" + +#define ETB_RAM_DEPTH_REG 0x004 +#define ETB_STATUS_REG 0x00c +#define ETB_RAM_READ_DATA_REG 0x010 +#define ETB_RAM_READ_POINTER 0x014 +#define ETB_RAM_WRITE_POINTER 0x018 +#define ETB_TRG 0x01c +#define ETB_CTL_REG 0x020 +#define ETB_RWD_REG 0x024 +#define ETB_FFSR 0x300 +#define ETB_FFCR 0x304 +#define ETB_ITMISCOP0 0xee0 +#define ETB_ITTRFLINACK 0xee4 +#define ETB_ITTRFLIN 0xee8 +#define ETB_ITATBDATA0 0xeeC +#define ETB_ITATBCTR2 0xef0 +#define ETB_ITATBCTR1 0xef4 +#define ETB_ITATBCTR0 0xef8 + +/* register description */ +/* STS - 0x00C */ +#define ETB_STATUS_RAM_FULL BIT(0) +/* CTL - 0x020 */ +#define ETB_CTL_CAPT_EN BIT(0) +/* FFCR - 0x304 */ +#define ETB_FFCR_EN_FTC BIT(0) +#define ETB_FFCR_FON_MAN BIT(6) +#define ETB_FFCR_STOP_FI BIT(12) +#define ETB_FFCR_STOP_TRIGGER BIT(13) + +#define ETB_FFCR_BIT 6 +#define ETB_FFSR_BIT 1 +#define ETB_FRAME_SIZE_WORDS 4 + +/** + * struct etb_drvdata - specifics associated to an ETB component + * @base: memory mapped base address for this component. + * @dev: the device entity associated to this component. + * @csdev: component vitals needed by the framework. + * @miscdev: specifics to handle "/dev/xyz.etb" entry. + * @clk: the clock this component is associated to. + * @spinlock: only one at a time pls. + * @in_use: synchronise user space access to etb buffer. + * @buf: area of memory where ETB buffer content gets sent. + * @buffer_depth: size of @buf. + * @enable: this ETB is being used. + * @trigger_cntr: amount of words to store after a trigger. + */ +struct etb_drvdata { + void __iomem *base; + struct device *dev; + struct coresight_device *csdev; + struct miscdevice miscdev; + struct clk *clk; + spinlock_t spinlock; + atomic_t in_use; + u8 *buf; + u32 buffer_depth; + bool enable; + u32 trigger_cntr; +}; + +static unsigned int etb_get_buffer_depth(struct etb_drvdata *drvdata) +{ + int ret; + u32 depth = 0; + + ret = clk_prepare_enable(drvdata->clk); + if (ret) + return ret; + + /* RO registers don't need locking */ + depth = readl_relaxed(drvdata->base + ETB_RAM_DEPTH_REG); + + clk_disable_unprepare(drvdata->clk); + return depth; +} + +static void etb_enable_hw(struct etb_drvdata *drvdata) +{ + int i; + u32 depth; + + CS_UNLOCK(drvdata->base); + + depth = drvdata->buffer_depth; + /* reset write RAM pointer address */ + writel_relaxed(0x0, drvdata->base + ETB_RAM_WRITE_POINTER); + /* clear entire RAM buffer */ + for (i = 0; i < depth; i++) + writel_relaxed(0x0, drvdata->base + ETB_RWD_REG); + + /* reset write RAM pointer address */ + writel_relaxed(0x0, drvdata->base + ETB_RAM_WRITE_POINTER); + /* reset read RAM pointer address */ + writel_relaxed(0x0, drvdata->base + ETB_RAM_READ_POINTER); + + writel_relaxed(drvdata->trigger_cntr, drvdata->base + ETB_TRG); + writel_relaxed(ETB_FFCR_EN_FTC | ETB_FFCR_STOP_TRIGGER, + drvdata->base + ETB_FFCR); + /* ETB trace capture enable */ + writel_relaxed(ETB_CTL_CAPT_EN, drvdata->base + ETB_CTL_REG); + + CS_LOCK(drvdata->base); +} + +static int etb_enable(struct coresight_device *csdev) +{ + struct etb_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); + int ret; + unsigned long flags; + + ret = clk_prepare_enable(drvdata->clk); + if (ret) + return ret; + + spin_lock_irqsave(&drvdata->spinlock, flags); + etb_enable_hw(drvdata); + drvdata->enable = true; + spin_unlock_irqrestore(&drvdata->spinlock, flags); + + dev_info(drvdata->dev, "ETB enabled\n"); + return 0; +} + +static void etb_disable_hw(struct etb_drvdata *drvdata) +{ + u32 ffcr; + + CS_UNLOCK(drvdata->base); + + ffcr = readl_relaxed(drvdata->base + ETB_FFCR); + /* stop formatter when a stop has completed */ + ffcr |= ETB_FFCR_STOP_FI; + writel_relaxed(ffcr, drvdata->base + ETB_FFCR); + /* manually generate a flush of the system */ + ffcr |= ETB_FFCR_FON_MAN; + writel_relaxed(ffcr, drvdata->base + ETB_FFCR); + + if (coresight_timeout(drvdata->base, ETB_FFCR, ETB_FFCR_BIT, 0)) { + dev_err(drvdata->dev, + "timeout observed when probing at offset %#x\n", + ETB_FFCR); + } + + /* disable trace capture */ + writel_relaxed(0x0, drvdata->base + ETB_CTL_REG); + + if (coresight_timeout(drvdata->base, ETB_FFSR, ETB_FFSR_BIT, 1)) { + dev_err(drvdata->dev, + "timeout observed when probing at offset %#x\n", + ETB_FFCR); + } + + CS_LOCK(drvdata->base); +} + +static void etb_dump_hw(struct etb_drvdata *drvdata) +{ + int i; + u8 *buf_ptr; + u32 read_data, depth; + u32 read_ptr, write_ptr; + u32 frame_off, frame_endoff; + + CS_UNLOCK(drvdata->base); + + read_ptr = readl_relaxed(drvdata->base + ETB_RAM_READ_POINTER); + write_ptr = readl_relaxed(drvdata->base + ETB_RAM_WRITE_POINTER); + + frame_off = write_ptr % ETB_FRAME_SIZE_WORDS; + frame_endoff = ETB_FRAME_SIZE_WORDS - frame_off; + if (frame_off) { + dev_err(drvdata->dev, + "write_ptr: %lu not aligned to formatter frame size\n", + (unsigned long)write_ptr); + dev_err(drvdata->dev, "frameoff: %lu, frame_endoff: %lu\n", + (unsigned long)frame_off, (unsigned long)frame_endoff); + write_ptr += frame_endoff; + } + + if ((readl_relaxed(drvdata->base + ETB_STATUS_REG) + & ETB_STATUS_RAM_FULL) == 0) + writel_relaxed(0x0, drvdata->base + ETB_RAM_READ_POINTER); + else + writel_relaxed(write_ptr, drvdata->base + ETB_RAM_READ_POINTER); + + depth = drvdata->buffer_depth; + buf_ptr = drvdata->buf; + for (i = 0; i < depth; i++) { + 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; + } + + if (frame_off) { + buf_ptr -= (frame_endoff * 4); + for (i = 0; i < frame_endoff; i++) { + *buf_ptr++ = 0x0; + *buf_ptr++ = 0x0; + *buf_ptr++ = 0x0; + *buf_ptr++ = 0x0; + } + } + + writel_relaxed(read_ptr, drvdata->base + ETB_RAM_READ_POINTER); + + CS_LOCK(drvdata->base); +} + +static void etb_disable(struct coresight_device *csdev) +{ + struct etb_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); + unsigned long flags; + + spin_lock_irqsave(&drvdata->spinlock, flags); + etb_disable_hw(drvdata); + etb_dump_hw(drvdata); + drvdata->enable = false; + spin_unlock_irqrestore(&drvdata->spinlock, flags); + + clk_disable_unprepare(drvdata->clk); + + dev_info(drvdata->dev, "ETB disabled\n"); +} + +static const struct coresight_ops_sink etb_sink_ops = { + .enable = etb_enable, + .disable = etb_disable, +}; + +static const struct coresight_ops etb_cs_ops = { + .sink_ops = &etb_sink_ops, +}; + +static void etb_dump(struct etb_drvdata *drvdata) +{ + unsigned long flags; + + spin_lock_irqsave(&drvdata->spinlock, flags); + if (drvdata->enable) { + etb_disable_hw(drvdata); + etb_dump_hw(drvdata); + etb_enable_hw(drvdata); + } + spin_unlock_irqrestore(&drvdata->spinlock, flags); + + dev_info(drvdata->dev, "ETB dumped\n"); +} + +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)) + return -EBUSY; + + dev_dbg(drvdata->dev, "%s: successfully opened\n", __func__); + return 0; +} + +static ssize_t etb_read(struct file *file, char __user *data, + size_t len, loff_t *ppos) +{ + u32 depth; + struct etb_drvdata *drvdata = container_of(file->private_data, + struct etb_drvdata, miscdev); + + etb_dump(drvdata); + + depth = drvdata->buffer_depth; + if (*ppos + len > depth * 4) + len = depth * 4 - *ppos; + + if (copy_to_user(data, drvdata->buf + *ppos, len)) { + dev_dbg(drvdata->dev, "%s: copy_to_user failed\n", __func__); + return -EFAULT; + } + + *ppos += len; + + dev_dbg(drvdata->dev, "%s: %zu bytes copied, %d bytes left\n", + __func__, len, (int)(depth * 4 - *ppos)); + return len; +} + +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); + + dev_dbg(drvdata->dev, "%s: released\n", __func__); + return 0; +} + +static const struct file_operations etb_fops = { + .owner = THIS_MODULE, + .open = etb_open, + .read = etb_read, + .release = etb_release, + .llseek = no_llseek, +}; + +static ssize_t status_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret; + unsigned long flags; + u32 etb_rdr, etb_sr, etb_rrp, etb_rwp; + u32 etb_trg, etb_cr, etb_ffsr, etb_ffcr; + struct etb_drvdata *drvdata = dev_get_drvdata(dev->parent); + + ret = clk_prepare_enable(drvdata->clk); + if (ret) + goto out; + + spin_lock_irqsave(&drvdata->spinlock, flags); + CS_UNLOCK(drvdata->base); + + etb_rdr = readl_relaxed(drvdata->base + ETB_RAM_DEPTH_REG); + etb_sr = readl_relaxed(drvdata->base + ETB_STATUS_REG); + etb_rrp = readl_relaxed(drvdata->base + ETB_RAM_READ_POINTER); + etb_rwp = readl_relaxed(drvdata->base + ETB_RAM_WRITE_POINTER); + etb_trg = readl_relaxed(drvdata->base + ETB_TRG); + etb_cr = readl_relaxed(drvdata->base + ETB_CTL_REG); + etb_ffsr = readl_relaxed(drvdata->base + ETB_FFSR); + etb_ffcr = readl_relaxed(drvdata->base + ETB_FFCR); + + CS_LOCK(drvdata->base); + spin_unlock_irqrestore(&drvdata->spinlock, flags); + + clk_disable_unprepare(drvdata->clk); + + return sprintf(buf, + "Depth:\t\t0x%x\n" + "Status:\t\t0x%x\n" + "RAM read ptr:\t0x%x\n" + "RAM wrt ptr:\t0x%x\n" + "Trigger cnt:\t0x%x\n" + "Control:\t0x%x\n" + "Flush status:\t0x%x\n" + "Flush ctrl:\t0x%x\n", + etb_rdr, etb_sr, etb_rrp, etb_rwp, + etb_trg, etb_cr, etb_ffsr, etb_ffcr); +out: + return -EINVAL; +} +static DEVICE_ATTR_RO(status); + +static ssize_t trigger_cntr_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct etb_drvdata *drvdata = dev_get_drvdata(dev->parent); + unsigned long val = drvdata->trigger_cntr; + + return sprintf(buf, "%#lx\n", val); +} + +static ssize_t trigger_cntr_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int ret; + unsigned long val; + struct etb_drvdata *drvdata = dev_get_drvdata(dev->parent); + + ret = kstrtoul(buf, 16, &val); + if (ret) + return ret; + + drvdata->trigger_cntr = val; + return size; +} +static DEVICE_ATTR_RW(trigger_cntr); + +static struct attribute *coresight_etb_attrs[] = { + &dev_attr_trigger_cntr.attr, + &dev_attr_status.attr, + NULL, +}; +ATTRIBUTE_GROUPS(coresight_etb); + +static int etb_probe(struct amba_device *adev, const struct amba_id *id) +{ + int ret; + void __iomem *base; + struct device *dev = &adev->dev; + struct coresight_platform_data *pdata = NULL; + struct etb_drvdata *drvdata; + struct resource *res = &adev->res; + struct coresight_desc *desc; + struct device_node *np = adev->dev.of_node; + + if (np) { + pdata = of_get_coresight_platform_data(dev, np); + if (IS_ERR(pdata)) + return PTR_ERR(pdata); + adev->dev.platform_data = pdata; + } + + drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + drvdata->dev = &adev->dev; + dev_set_drvdata(dev, drvdata); + + /* validity for the resource is already checked by the AMBA core */ + base = devm_ioremap_resource(dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + drvdata->base = base; + + spin_lock_init(&drvdata->spinlock); + + drvdata->clk = adev->pclk; + ret = clk_prepare_enable(drvdata->clk); + if (ret) + return ret; + + drvdata->buffer_depth = etb_get_buffer_depth(drvdata); + clk_disable_unprepare(drvdata->clk); + + if (drvdata->buffer_depth < 0) + return -EINVAL; + + drvdata->buf = devm_kzalloc(dev, + drvdata->buffer_depth * 4, GFP_KERNEL); + if (!drvdata->buf) + return -ENOMEM; + + desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL); + if (!desc) + return -ENOMEM; + + desc->type = CORESIGHT_DEV_TYPE_SINK; + desc->subtype.sink_subtype = CORESIGHT_DEV_SUBTYPE_SINK_BUFFER; + desc->ops = &etb_cs_ops; + desc->pdata = pdata; + desc->dev = dev; + desc->groups = coresight_etb_groups; + drvdata->csdev = coresight_register(desc); + if (IS_ERR(drvdata->csdev)) + return PTR_ERR(drvdata->csdev); + + drvdata->miscdev.name = pdata->name; + drvdata->miscdev.minor = MISC_DYNAMIC_MINOR; + drvdata->miscdev.fops = &etb_fops; + ret = misc_register(&drvdata->miscdev); + if (ret) + goto err_misc_register; + + dev_info(dev, "ETB initialized\n"); + return 0; + +err_misc_register: + coresight_unregister(drvdata->csdev); + 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; +} + +static struct amba_id etb_ids[] = { + { + .id = 0x0003b907, + .mask = 0x0003ffff, + }, + { 0, 0}, +}; + +static struct amba_driver etb_driver = { + .drv = { + .name = "coresight-etb10", + .owner = THIS_MODULE, + }, + .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"); diff --git a/drivers/hwtracing/coresight/coresight-etm-cp14.c b/drivers/hwtracing/coresight/coresight-etm-cp14.c new file mode 100644 index 0000000..12a2206 --- /dev/null +++ b/drivers/hwtracing/coresight/coresight-etm-cp14.c @@ -0,0 +1,591 @@ +/* Copyright (c) 2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include + +#include "coresight-etm.h" + +int etm_readl_cp14(u32 reg, unsigned int *val) +{ + switch (reg) { + case ETMCR: + *val = etm_read(ETMCR); + return 0; + case ETMCCR: + *val = etm_read(ETMCCR); + return 0; + case ETMTRIGGER: + *val = etm_read(ETMTRIGGER); + return 0; + case ETMSR: + *val = etm_read(ETMSR); + return 0; + case ETMSCR: + *val = etm_read(ETMSCR); + return 0; + case ETMTSSCR: + *val = etm_read(ETMTSSCR); + return 0; + case ETMTEEVR: + *val = etm_read(ETMTEEVR); + return 0; + case ETMTECR1: + *val = etm_read(ETMTECR1); + return 0; + case ETMFFLR: + *val = etm_read(ETMFFLR); + return 0; + case ETMACVRn(0): + *val = etm_read(ETMACVR0); + return 0; + case ETMACVRn(1): + *val = etm_read(ETMACVR1); + return 0; + case ETMACVRn(2): + *val = etm_read(ETMACVR2); + return 0; + case ETMACVRn(3): + *val = etm_read(ETMACVR3); + return 0; + case ETMACVRn(4): + *val = etm_read(ETMACVR4); + return 0; + case ETMACVRn(5): + *val = etm_read(ETMACVR5); + return 0; + case ETMACVRn(6): + *val = etm_read(ETMACVR6); + return 0; + case ETMACVRn(7): + *val = etm_read(ETMACVR7); + return 0; + case ETMACVRn(8): + *val = etm_read(ETMACVR8); + return 0; + case ETMACVRn(9): + *val = etm_read(ETMACVR9); + return 0; + case ETMACVRn(10): + *val = etm_read(ETMACVR10); + return 0; + case ETMACVRn(11): + *val = etm_read(ETMACVR11); + return 0; + case ETMACVRn(12): + *val = etm_read(ETMACVR12); + return 0; + case ETMACVRn(13): + *val = etm_read(ETMACVR13); + return 0; + case ETMACVRn(14): + *val = etm_read(ETMACVR14); + return 0; + case ETMACVRn(15): + *val = etm_read(ETMACVR15); + return 0; + case ETMACTRn(0): + *val = etm_read(ETMACTR0); + return 0; + case ETMACTRn(1): + *val = etm_read(ETMACTR1); + return 0; + case ETMACTRn(2): + *val = etm_read(ETMACTR2); + return 0; + case ETMACTRn(3): + *val = etm_read(ETMACTR3); + return 0; + case ETMACTRn(4): + *val = etm_read(ETMACTR4); + return 0; + case ETMACTRn(5): + *val = etm_read(ETMACTR5); + return 0; + case ETMACTRn(6): + *val = etm_read(ETMACTR6); + return 0; + case ETMACTRn(7): + *val = etm_read(ETMACTR7); + return 0; + case ETMACTRn(8): + *val = etm_read(ETMACTR8); + return 0; + case ETMACTRn(9): + *val = etm_read(ETMACTR9); + return 0; + case ETMACTRn(10): + *val = etm_read(ETMACTR10); + return 0; + case ETMACTRn(11): + *val = etm_read(ETMACTR11); + return 0; + case ETMACTRn(12): + *val = etm_read(ETMACTR12); + return 0; + case ETMACTRn(13): + *val = etm_read(ETMACTR13); + return 0; + case ETMACTRn(14): + *val = etm_read(ETMACTR14); + return 0; + case ETMACTRn(15): + *val = etm_read(ETMACTR15); + return 0; + case ETMCNTRLDVRn(0): + *val = etm_read(ETMCNTRLDVR0); + return 0; + case ETMCNTRLDVRn(1): + *val = etm_read(ETMCNTRLDVR1); + return 0; + case ETMCNTRLDVRn(2): + *val = etm_read(ETMCNTRLDVR2); + return 0; + case ETMCNTRLDVRn(3): + *val = etm_read(ETMCNTRLDVR3); + return 0; + case ETMCNTENRn(0): + *val = etm_read(ETMCNTENR0); + return 0; + case ETMCNTENRn(1): + *val = etm_read(ETMCNTENR1); + return 0; + case ETMCNTENRn(2): + *val = etm_read(ETMCNTENR2); + return 0; + case ETMCNTENRn(3): + *val = etm_read(ETMCNTENR3); + return 0; + case ETMCNTRLDEVRn(0): + *val = etm_read(ETMCNTRLDEVR0); + return 0; + case ETMCNTRLDEVRn(1): + *val = etm_read(ETMCNTRLDEVR1); + return 0; + case ETMCNTRLDEVRn(2): + *val = etm_read(ETMCNTRLDEVR2); + return 0; + case ETMCNTRLDEVRn(3): + *val = etm_read(ETMCNTRLDEVR3); + return 0; + case ETMCNTVRn(0): + *val = etm_read(ETMCNTVR0); + return 0; + case ETMCNTVRn(1): + *val = etm_read(ETMCNTVR1); + return 0; + case ETMCNTVRn(2): + *val = etm_read(ETMCNTVR2); + return 0; + case ETMCNTVRn(3): + *val = etm_read(ETMCNTVR3); + return 0; + case ETMSQ12EVR: + *val = etm_read(ETMSQ12EVR); + return 0; + case ETMSQ21EVR: + *val = etm_read(ETMSQ21EVR); + return 0; + case ETMSQ23EVR: + *val = etm_read(ETMSQ23EVR); + return 0; + case ETMSQ31EVR: + *val = etm_read(ETMSQ31EVR); + return 0; + case ETMSQ32EVR: + *val = etm_read(ETMSQ32EVR); + return 0; + case ETMSQ13EVR: + *val = etm_read(ETMSQ13EVR); + return 0; + case ETMSQR: + *val = etm_read(ETMSQR); + return 0; + case ETMEXTOUTEVRn(0): + *val = etm_read(ETMEXTOUTEVR0); + return 0; + case ETMEXTOUTEVRn(1): + *val = etm_read(ETMEXTOUTEVR1); + return 0; + case ETMEXTOUTEVRn(2): + *val = etm_read(ETMEXTOUTEVR2); + return 0; + case ETMEXTOUTEVRn(3): + *val = etm_read(ETMEXTOUTEVR3); + return 0; + case ETMCIDCVRn(0): + *val = etm_read(ETMCIDCVR0); + return 0; + case ETMCIDCVRn(1): + *val = etm_read(ETMCIDCVR1); + return 0; + case ETMCIDCVRn(2): + *val = etm_read(ETMCIDCVR2); + return 0; + case ETMCIDCMR: + *val = etm_read(ETMCIDCMR); + return 0; + case ETMIMPSPEC0: + *val = etm_read(ETMIMPSPEC0); + return 0; + case ETMIMPSPEC1: + *val = etm_read(ETMIMPSPEC1); + return 0; + case ETMIMPSPEC2: + *val = etm_read(ETMIMPSPEC2); + return 0; + case ETMIMPSPEC3: + *val = etm_read(ETMIMPSPEC3); + return 0; + case ETMIMPSPEC4: + *val = etm_read(ETMIMPSPEC4); + return 0; + case ETMIMPSPEC5: + *val = etm_read(ETMIMPSPEC5); + return 0; + case ETMIMPSPEC6: + *val = etm_read(ETMIMPSPEC6); + return 0; + case ETMIMPSPEC7: + *val = etm_read(ETMIMPSPEC7); + return 0; + case ETMSYNCFR: + *val = etm_read(ETMSYNCFR); + return 0; + case ETMIDR: + *val = etm_read(ETMIDR); + return 0; + case ETMCCER: + *val = etm_read(ETMCCER); + return 0; + case ETMEXTINSELR: + *val = etm_read(ETMEXTINSELR); + return 0; + case ETMTESSEICR: + *val = etm_read(ETMTESSEICR); + return 0; + case ETMEIBCR: + *val = etm_read(ETMEIBCR); + return 0; + case ETMTSEVR: + *val = etm_read(ETMTSEVR); + return 0; + case ETMAUXCR: + *val = etm_read(ETMAUXCR); + return 0; + case ETMTRACEIDR: + *val = etm_read(ETMTRACEIDR); + return 0; + case ETMVMIDCVR: + *val = etm_read(ETMVMIDCVR); + return 0; + case ETMOSLSR: + *val = etm_read(ETMOSLSR); + return 0; + case ETMOSSRR: + *val = etm_read(ETMOSSRR); + return 0; + case ETMPDCR: + *val = etm_read(ETMPDCR); + return 0; + case ETMPDSR: + *val = etm_read(ETMPDSR); + return 0; + default: + *val = 0; + return -EINVAL; + } +} + +int etm_writel_cp14(u32 reg, u32 val) +{ + switch (reg) { + case ETMCR: + etm_write(val, ETMCR); + break; + case ETMTRIGGER: + etm_write(val, ETMTRIGGER); + break; + case ETMSR: + etm_write(val, ETMSR); + break; + case ETMTSSCR: + etm_write(val, ETMTSSCR); + break; + case ETMTEEVR: + etm_write(val, ETMTEEVR); + break; + case ETMTECR1: + etm_write(val, ETMTECR1); + break; + case ETMFFLR: + etm_write(val, ETMFFLR); + break; + case ETMACVRn(0): + etm_write(val, ETMACVR0); + break; + case ETMACVRn(1): + etm_write(val, ETMACVR1); + break; + case ETMACVRn(2): + etm_write(val, ETMACVR2); + break; + case ETMACVRn(3): + etm_write(val, ETMACVR3); + break; + case ETMACVRn(4): + etm_write(val, ETMACVR4); + break; + case ETMACVRn(5): + etm_write(val, ETMACVR5); + break; + case ETMACVRn(6): + etm_write(val, ETMACVR6); + break; + case ETMACVRn(7): + etm_write(val, ETMACVR7); + break; + case ETMACVRn(8): + etm_write(val, ETMACVR8); + break; + case ETMACVRn(9): + etm_write(val, ETMACVR9); + break; + case ETMACVRn(10): + etm_write(val, ETMACVR10); + break; + case ETMACVRn(11): + etm_write(val, ETMACVR11); + break; + case ETMACVRn(12): + etm_write(val, ETMACVR12); + break; + case ETMACVRn(13): + etm_write(val, ETMACVR13); + break; + case ETMACVRn(14): + etm_write(val, ETMACVR14); + break; + case ETMACVRn(15): + etm_write(val, ETMACVR15); + break; + case ETMACTRn(0): + etm_write(val, ETMACTR0); + break; + case ETMACTRn(1): + etm_write(val, ETMACTR1); + break; + case ETMACTRn(2): + etm_write(val, ETMACTR2); + break; + case ETMACTRn(3): + etm_write(val, ETMACTR3); + break; + case ETMACTRn(4): + etm_write(val, ETMACTR4); + break; + case ETMACTRn(5): + etm_write(val, ETMACTR5); + break; + case ETMACTRn(6): + etm_write(val, ETMACTR6); + break; + case ETMACTRn(7): + etm_write(val, ETMACTR7); + break; + case ETMACTRn(8): + etm_write(val, ETMACTR8); + break; + case ETMACTRn(9): + etm_write(val, ETMACTR9); + break; + case ETMACTRn(10): + etm_write(val, ETMACTR10); + break; + case ETMACTRn(11): + etm_write(val, ETMACTR11); + break; + case ETMACTRn(12): + etm_write(val, ETMACTR12); + break; + case ETMACTRn(13): + etm_write(val, ETMACTR13); + break; + case ETMACTRn(14): + etm_write(val, ETMACTR14); + break; + case ETMACTRn(15): + etm_write(val, ETMACTR15); + break; + case ETMCNTRLDVRn(0): + etm_write(val, ETMCNTRLDVR0); + break; + case ETMCNTRLDVRn(1): + etm_write(val, ETMCNTRLDVR1); + break; + case ETMCNTRLDVRn(2): + etm_write(val, ETMCNTRLDVR2); + break; + case ETMCNTRLDVRn(3): + etm_write(val, ETMCNTRLDVR3); + break; + case ETMCNTENRn(0): + etm_write(val, ETMCNTENR0); + break; + case ETMCNTENRn(1): + etm_write(val, ETMCNTENR1); + break; + case ETMCNTENRn(2): + etm_write(val, ETMCNTENR2); + break; + case ETMCNTENRn(3): + etm_write(val, ETMCNTENR3); + break; + case ETMCNTRLDEVRn(0): + etm_write(val, ETMCNTRLDEVR0); + break; + case ETMCNTRLDEVRn(1): + etm_write(val, ETMCNTRLDEVR1); + break; + case ETMCNTRLDEVRn(2): + etm_write(val, ETMCNTRLDEVR2); + break; + case ETMCNTRLDEVRn(3): + etm_write(val, ETMCNTRLDEVR3); + break; + case ETMCNTVRn(0): + etm_write(val, ETMCNTVR0); + break; + case ETMCNTVRn(1): + etm_write(val, ETMCNTVR1); + break; + case ETMCNTVRn(2): + etm_write(val, ETMCNTVR2); + break; + case ETMCNTVRn(3): + etm_write(val, ETMCNTVR3); + break; + case ETMSQ12EVR: + etm_write(val, ETMSQ12EVR); + break; + case ETMSQ21EVR: + etm_write(val, ETMSQ21EVR); + break; + case ETMSQ23EVR: + etm_write(val, ETMSQ23EVR); + break; + case ETMSQ31EVR: + etm_write(val, ETMSQ31EVR); + break; + case ETMSQ32EVR: + etm_write(val, ETMSQ32EVR); + break; + case ETMSQ13EVR: + etm_write(val, ETMSQ13EVR); + break; + case ETMSQR: + etm_write(val, ETMSQR); + break; + case ETMEXTOUTEVRn(0): + etm_write(val, ETMEXTOUTEVR0); + break; + case ETMEXTOUTEVRn(1): + etm_write(val, ETMEXTOUTEVR1); + break; + case ETMEXTOUTEVRn(2): + etm_write(val, ETMEXTOUTEVR2); + break; + case ETMEXTOUTEVRn(3): + etm_write(val, ETMEXTOUTEVR3); + break; + case ETMCIDCVRn(0): + etm_write(val, ETMCIDCVR0); + break; + case ETMCIDCVRn(1): + etm_write(val, ETMCIDCVR1); + break; + case ETMCIDCVRn(2): + etm_write(val, ETMCIDCVR2); + break; + case ETMCIDCMR: + etm_write(val, ETMCIDCMR); + break; + case ETMIMPSPEC0: + etm_write(val, ETMIMPSPEC0); + break; + case ETMIMPSPEC1: + etm_write(val, ETMIMPSPEC1); + break; + case ETMIMPSPEC2: + etm_write(val, ETMIMPSPEC2); + break; + case ETMIMPSPEC3: + etm_write(val, ETMIMPSPEC3); + break; + case ETMIMPSPEC4: + etm_write(val, ETMIMPSPEC4); + break; + case ETMIMPSPEC5: + etm_write(val, ETMIMPSPEC5); + break; + case ETMIMPSPEC6: + etm_write(val, ETMIMPSPEC6); + break; + case ETMIMPSPEC7: + etm_write(val, ETMIMPSPEC7); + break; + case ETMSYNCFR: + etm_write(val, ETMSYNCFR); + break; + case ETMEXTINSELR: + etm_write(val, ETMEXTINSELR); + break; + case ETMTESSEICR: + etm_write(val, ETMTESSEICR); + break; + case ETMEIBCR: + etm_write(val, ETMEIBCR); + break; + case ETMTSEVR: + etm_write(val, ETMTSEVR); + break; + case ETMAUXCR: + etm_write(val, ETMAUXCR); + break; + case ETMTRACEIDR: + etm_write(val, ETMTRACEIDR); + break; + case ETMVMIDCVR: + etm_write(val, ETMVMIDCVR); + break; + case ETMOSLAR: + etm_write(val, ETMOSLAR); + break; + case ETMOSSRR: + etm_write(val, ETMOSSRR); + break; + case ETMPDCR: + etm_write(val, ETMPDCR); + break; + case ETMPDSR: + etm_write(val, ETMPDSR); + break; + default: + return -EINVAL; + } + + return 0; +} diff --git a/drivers/hwtracing/coresight/coresight-etm.h b/drivers/hwtracing/coresight/coresight-etm.h new file mode 100644 index 0000000..501c5fa --- /dev/null +++ b/drivers/hwtracing/coresight/coresight-etm.h @@ -0,0 +1,251 @@ +/* Copyright (c) 2014-2015, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _CORESIGHT_CORESIGHT_ETM_H +#define _CORESIGHT_CORESIGHT_ETM_H + +#include +#include "coresight-priv.h" + +/* + * Device registers: + * 0x000 - 0x2FC: Trace registers + * 0x300 - 0x314: Management registers + * 0x318 - 0xEFC: Trace registers + * + * Coresight registers + * 0xF00 - 0xF9C: Management registers + * 0xFA0 - 0xFA4: Management registers in PFTv1.0 + * Trace registers in PFTv1.1 + * 0xFA8 - 0xFFC: Management registers + */ + +/* Trace registers (0x000-0x2FC) */ +#define ETMCR 0x000 +#define ETMCCR 0x004 +#define ETMTRIGGER 0x008 +#define ETMSR 0x010 +#define ETMSCR 0x014 +#define ETMTSSCR 0x018 +#define ETMTECR2 0x01c +#define ETMTEEVR 0x020 +#define ETMTECR1 0x024 +#define ETMFFLR 0x02c +#define ETMACVRn(n) (0x040 + (n * 4)) +#define ETMACTRn(n) (0x080 + (n * 4)) +#define ETMCNTRLDVRn(n) (0x140 + (n * 4)) +#define ETMCNTENRn(n) (0x150 + (n * 4)) +#define ETMCNTRLDEVRn(n) (0x160 + (n * 4)) +#define ETMCNTVRn(n) (0x170 + (n * 4)) +#define ETMSQ12EVR 0x180 +#define ETMSQ21EVR 0x184 +#define ETMSQ23EVR 0x188 +#define ETMSQ31EVR 0x18c +#define ETMSQ32EVR 0x190 +#define ETMSQ13EVR 0x194 +#define ETMSQR 0x19c +#define ETMEXTOUTEVRn(n) (0x1a0 + (n * 4)) +#define ETMCIDCVRn(n) (0x1b0 + (n * 4)) +#define ETMCIDCMR 0x1bc +#define ETMIMPSPEC0 0x1c0 +#define ETMIMPSPEC1 0x1c4 +#define ETMIMPSPEC2 0x1c8 +#define ETMIMPSPEC3 0x1cc +#define ETMIMPSPEC4 0x1d0 +#define ETMIMPSPEC5 0x1d4 +#define ETMIMPSPEC6 0x1d8 +#define ETMIMPSPEC7 0x1dc +#define ETMSYNCFR 0x1e0 +#define ETMIDR 0x1e4 +#define ETMCCER 0x1e8 +#define ETMEXTINSELR 0x1ec +#define ETMTESSEICR 0x1f0 +#define ETMEIBCR 0x1f4 +#define ETMTSEVR 0x1f8 +#define ETMAUXCR 0x1fc +#define ETMTRACEIDR 0x200 +#define ETMVMIDCVR 0x240 +/* Management registers (0x300-0x314) */ +#define ETMOSLAR 0x300 +#define ETMOSLSR 0x304 +#define ETMOSSRR 0x308 +#define ETMPDCR 0x310 +#define ETMPDSR 0x314 +#define ETM_MAX_ADDR_CMP 16 +#define ETM_MAX_CNTR 4 +#define ETM_MAX_CTXID_CMP 3 + +/* Register definition */ +/* ETMCR - 0x00 */ +#define ETMCR_PWD_DWN BIT(0) +#define ETMCR_STALL_MODE BIT(7) +#define ETMCR_ETM_PRG BIT(10) +#define ETMCR_ETM_EN BIT(11) +#define ETMCR_CYC_ACC BIT(12) +#define ETMCR_CTXID_SIZE (BIT(14)|BIT(15)) +#define ETMCR_TIMESTAMP_EN BIT(28) +/* ETMCCR - 0x04 */ +#define ETMCCR_FIFOFULL BIT(23) +/* ETMPDCR - 0x310 */ +#define ETMPDCR_PWD_UP BIT(3) +/* ETMTECR1 - 0x024 */ +#define ETMTECR1_ADDR_COMP_1 BIT(0) +#define ETMTECR1_INC_EXC BIT(24) +#define ETMTECR1_START_STOP BIT(25) +/* ETMCCER - 0x1E8 */ +#define ETMCCER_TIMESTAMP BIT(22) + +#define ETM_MODE_EXCLUDE BIT(0) +#define ETM_MODE_CYCACC BIT(1) +#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_SQR_MASK 0x3 +#define ETM_TRACEID_MASK 0x3f +#define ETM_EVENT_MASK 0x1ffff +#define ETM_SYNC_MASK 0xfff +#define ETM_ALL_MASK 0xffffffff + +#define ETMSR_PROG_BIT 1 +#define ETM_SEQ_STATE_MAX_VAL (0x2) +#define PORT_SIZE_MASK (GENMASK(21, 21) | GENMASK(6, 4)) + +#define ETM_HARD_WIRE_RES_A /* Hard wired, always true */ \ + ((0x0f << 0) | \ + /* Resource index A */ \ + (0x06 << 4)) + +#define ETM_ADD_COMP_0 /* Single addr comparator 1 */ \ + ((0x00 << 7) | \ + /* Resource index B */ \ + (0x00 << 11)) + +#define ETM_EVENT_NOT_A BIT(14) /* NOT(A) */ + +#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. + * @csdev: component vitals needed by the framework. + * @clk: the clock this component is associated to. + * @spinlock: only one at a time pls. + * @cpu: the cpu this component is affined to. + * @port_size: port size as reported by ETMCR bit 4-6 and 21. + * @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. + * @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. + * @fifofull_level: setting for register ETMFFLR. + * @addr_idx: index for the address comparator selection. + * @addr_val: value for address comparator register. + * @addr_acctype: access type for address comparator register. + * @addr_type: current status of the comparator register. + * @cntr_idx: index for the counter register selection. + * @cntr_rld_val: reload value of a counter register. + * @cntr_event: control for counter enable register. + * @cntr_rld_event: value for counter reload event register. + * @cntr_val: counter value register. + * @seq_12_event: event causing the transition from 1 to 2. + * @seq_21_event: event causing the transition from 2 to 1. + * @seq_23_event: event causing the transition from 2 to 3. + * @seq_31_event: event causing the transition from 3 to 1. + * @seq_32_event: event causing the transition from 3 to 2. + * @seq_13_event: event causing the transition from 1 to 3. + * @seq_curr_state: current value of the sequencer register. + * @ctxid_idx: index for the context ID registers. + * @ctxid_val: value for the context ID to trigger on. + * @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. + */ +struct etm_drvdata { + void __iomem *base; + struct device *dev; + struct coresight_device *csdev; + struct clk *clk; + 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; + u32 mode; + u32 ctrl; + u32 trigger_event; + u32 startstop_ctrl; + u32 enable_event; + u32 enable_ctrl1; + u32 fifofull_level; + u8 addr_idx; + u32 addr_val[ETM_MAX_ADDR_CMP]; + u32 addr_acctype[ETM_MAX_ADDR_CMP]; + u32 addr_type[ETM_MAX_ADDR_CMP]; + u8 cntr_idx; + u32 cntr_rld_val[ETM_MAX_CNTR]; + u32 cntr_event[ETM_MAX_CNTR]; + u32 cntr_rld_event[ETM_MAX_CNTR]; + u32 cntr_val[ETM_MAX_CNTR]; + u32 seq_12_event; + u32 seq_21_event; + u32 seq_23_event; + u32 seq_31_event; + u32 seq_32_event; + u32 seq_13_event; + u32 seq_curr_state; + u8 ctxid_idx; + u32 ctxid_val[ETM_MAX_CTXID_CMP]; + u32 ctxid_mask; + u32 sync_freq; + u32 timestamp_event; +}; + +enum etm_addr_type { + ETM_ADDR_TYPE_NONE, + ETM_ADDR_TYPE_SINGLE, + ETM_ADDR_TYPE_RANGE, + ETM_ADDR_TYPE_START, + ETM_ADDR_TYPE_STOP, +}; +#endif diff --git a/drivers/hwtracing/coresight/coresight-etm3x.c b/drivers/hwtracing/coresight/coresight-etm3x.c new file mode 100644 index 0000000..c965f57 --- /dev/null +++ b/drivers/hwtracing/coresight/coresight-etm3x.c @@ -0,0 +1,1932 @@ +/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "coresight-etm.h" + +static int boot_enable; +module_param_named(boot_enable, boot_enable, int, S_IRUGO); + +/* The number of ETM/PTM currently registered */ +static int etm_count; +static struct etm_drvdata *etmdrvdata[NR_CPUS]; + +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; +} + +/* + * Memory mapped writes to clear os lock are not supported on some processors + * and OS lock must be unlocked before any memory mapped access on such + * processors, otherwise memory mapped reads/writes will be invalid. + */ +static void etm_os_unlock(void *info) +{ + struct etm_drvdata *drvdata = (struct etm_drvdata *)info; + /* Writing any value to ETMOSLAR unlocks the trace registers */ + etm_writel(drvdata, 0x0, ETMOSLAR); + isb(); +} + +static void etm_set_pwrdwn(struct etm_drvdata *drvdata) +{ + u32 etmcr; + + /* Ensure pending cp14 accesses complete before setting pwrdwn */ + mb(); + isb(); + etmcr = etm_readl(drvdata, ETMCR); + etmcr |= ETMCR_PWD_DWN; + etm_writel(drvdata, etmcr, ETMCR); +} + +static void etm_clr_pwrdwn(struct etm_drvdata *drvdata) +{ + u32 etmcr; + + etmcr = etm_readl(drvdata, ETMCR); + etmcr &= ~ETMCR_PWD_DWN; + etm_writel(drvdata, etmcr, ETMCR); + /* Ensure pwrup completes before subsequent cp14 accesses */ + mb(); + isb(); +} + +static void etm_set_pwrup(struct etm_drvdata *drvdata) +{ + u32 etmpdcr; + + etmpdcr = readl_relaxed(drvdata->base + ETMPDCR); + etmpdcr |= ETMPDCR_PWD_UP; + writel_relaxed(etmpdcr, drvdata->base + ETMPDCR); + /* Ensure pwrup completes before subsequent cp14 accesses */ + mb(); + isb(); +} + +static void etm_clr_pwrup(struct etm_drvdata *drvdata) +{ + u32 etmpdcr; + + /* Ensure pending cp14 accesses complete before clearing pwrup */ + mb(); + isb(); + etmpdcr = readl_relaxed(drvdata->base + ETMPDCR); + etmpdcr &= ~ETMPDCR_PWD_UP; + writel_relaxed(etmpdcr, drvdata->base + ETMPDCR); +} + +/** + * coresight_timeout_etm - loop until a bit has changed to a specific state. + * @drvdata: etm's private data structure. + * @offset: address of a register, starting from @addr. + * @position: the position of the bit of interest. + * @value: the value the bit should have. + * + * Basically the same as @coresight_timeout except for the register access + * method where we have to account for CP14 configurations. + + * Return: 0 as soon as the bit has taken the desired state or -EAGAIN if + * TIMEOUT_US has elapsed, which ever happens first. + */ + +static int coresight_timeout_etm(struct etm_drvdata *drvdata, u32 offset, + int position, int value) +{ + int i; + u32 val; + + for (i = TIMEOUT_US; i > 0; i--) { + val = etm_readl(drvdata, offset); + /* Waiting on the bit to go from 0 to 1 */ + if (value) { + if (val & BIT(position)) + return 0; + /* Waiting on the bit to go from 1 to 0 */ + } else { + if (!(val & BIT(position))) + return 0; + } + + /* + * Delay is arbitrary - the specification doesn't say how long + * we are expected to wait. Extra check required to make sure + * we don't wait needlessly on the last iteration. + */ + if (i - 1) + udelay(1); + } + + return -EAGAIN; +} + + +static void etm_set_prog(struct etm_drvdata *drvdata) +{ + u32 etmcr; + + etmcr = etm_readl(drvdata, ETMCR); + etmcr |= ETMCR_ETM_PRG; + etm_writel(drvdata, etmcr, ETMCR); + /* + * Recommended by spec for cp14 accesses to ensure etmcr write is + * complete before polling etmsr + */ + isb(); + if (coresight_timeout_etm(drvdata, ETMSR, ETMSR_PROG_BIT, 1)) { + dev_err(drvdata->dev, + "timeout observed when probing at offset %#x\n", ETMSR); + } +} + +static void etm_clr_prog(struct etm_drvdata *drvdata) +{ + u32 etmcr; + + etmcr = etm_readl(drvdata, ETMCR); + etmcr &= ~ETMCR_ETM_PRG; + etm_writel(drvdata, etmcr, ETMCR); + /* + * Recommended by spec for cp14 accesses to ensure etmcr write is + * complete before polling etmsr + */ + isb(); + if (coresight_timeout_etm(drvdata, ETMSR, ETMSR_PROG_BIT, 0)) { + dev_err(drvdata->dev, + "timeout observed when probing at offset %#x\n", ETMSR); + } +} + +static void etm_set_default(struct etm_drvdata *drvdata) +{ + int i; + + drvdata->trigger_event = ETM_DEFAULT_EVENT_VAL; + drvdata->enable_event = ETM_HARD_WIRE_RES_A; + + drvdata->seq_12_event = ETM_DEFAULT_EVENT_VAL; + drvdata->seq_21_event = ETM_DEFAULT_EVENT_VAL; + drvdata->seq_23_event = ETM_DEFAULT_EVENT_VAL; + drvdata->seq_31_event = ETM_DEFAULT_EVENT_VAL; + drvdata->seq_32_event = ETM_DEFAULT_EVENT_VAL; + drvdata->seq_13_event = ETM_DEFAULT_EVENT_VAL; + drvdata->timestamp_event = ETM_DEFAULT_EVENT_VAL; + + for (i = 0; i < drvdata->nr_cntr; i++) { + drvdata->cntr_rld_val[i] = 0x0; + drvdata->cntr_event[i] = ETM_DEFAULT_EVENT_VAL; + drvdata->cntr_rld_event[i] = ETM_DEFAULT_EVENT_VAL; + drvdata->cntr_val[i] = 0x0; + } + + drvdata->seq_curr_state = 0x0; + drvdata->ctxid_idx = 0x0; + for (i = 0; i < drvdata->nr_ctxid_cmp; i++) + drvdata->ctxid_val[i] = 0x0; + drvdata->ctxid_mask = 0x0; +} + +static void etm_enable_hw(void *info) +{ + int i; + u32 etmcr; + struct etm_drvdata *drvdata = info; + + CS_UNLOCK(drvdata->base); + + /* Turn engine on */ + etm_clr_pwrdwn(drvdata); + /* Apply power to trace registers */ + etm_set_pwrup(drvdata); + /* Make sure all registers are accessible */ + etm_os_unlock(drvdata); + + etm_set_prog(drvdata); + + etmcr = etm_readl(drvdata, ETMCR); + etmcr &= (ETMCR_PWD_DWN | ETMCR_ETM_PRG); + etmcr |= drvdata->port_size; + etm_writel(drvdata, drvdata->ctrl | etmcr, ETMCR); + etm_writel(drvdata, drvdata->trigger_event, ETMTRIGGER); + etm_writel(drvdata, drvdata->startstop_ctrl, ETMTSSCR); + etm_writel(drvdata, drvdata->enable_event, ETMTEEVR); + etm_writel(drvdata, drvdata->enable_ctrl1, ETMTECR1); + etm_writel(drvdata, drvdata->fifofull_level, ETMFFLR); + for (i = 0; i < drvdata->nr_addr_cmp; i++) { + etm_writel(drvdata, drvdata->addr_val[i], ETMACVRn(i)); + etm_writel(drvdata, drvdata->addr_acctype[i], ETMACTRn(i)); + } + for (i = 0; i < drvdata->nr_cntr; i++) { + etm_writel(drvdata, drvdata->cntr_rld_val[i], ETMCNTRLDVRn(i)); + etm_writel(drvdata, drvdata->cntr_event[i], ETMCNTENRn(i)); + etm_writel(drvdata, drvdata->cntr_rld_event[i], + ETMCNTRLDEVRn(i)); + etm_writel(drvdata, drvdata->cntr_val[i], ETMCNTVRn(i)); + } + etm_writel(drvdata, drvdata->seq_12_event, ETMSQ12EVR); + etm_writel(drvdata, drvdata->seq_21_event, ETMSQ21EVR); + etm_writel(drvdata, drvdata->seq_23_event, ETMSQ23EVR); + etm_writel(drvdata, drvdata->seq_31_event, ETMSQ31EVR); + etm_writel(drvdata, drvdata->seq_32_event, ETMSQ32EVR); + etm_writel(drvdata, drvdata->seq_13_event, ETMSQ13EVR); + etm_writel(drvdata, drvdata->seq_curr_state, ETMSQR); + for (i = 0; i < drvdata->nr_ext_out; i++) + etm_writel(drvdata, ETM_DEFAULT_EVENT_VAL, ETMEXTOUTEVRn(i)); + for (i = 0; i < drvdata->nr_ctxid_cmp; i++) + etm_writel(drvdata, drvdata->ctxid_val[i], ETMCIDCVRn(i)); + etm_writel(drvdata, drvdata->ctxid_mask, ETMCIDCMR); + etm_writel(drvdata, drvdata->sync_freq, ETMSYNCFR); + /* No external input selected */ + etm_writel(drvdata, 0x0, ETMEXTINSELR); + etm_writel(drvdata, drvdata->timestamp_event, ETMTSEVR); + /* No auxiliary control selected */ + etm_writel(drvdata, 0x0, ETMAUXCR); + etm_writel(drvdata, drvdata->traceid, ETMTRACEIDR); + /* No VMID comparator value selected */ + etm_writel(drvdata, 0x0, ETMVMIDCVR); + + /* Ensures trace output is enabled from this ETM */ + etm_writel(drvdata, drvdata->ctrl | ETMCR_ETM_EN | etmcr, ETMCR); + + etm_clr_prog(drvdata); + CS_LOCK(drvdata->base); + + dev_dbg(drvdata->dev, "cpu: %d enable smp call done\n", drvdata->cpu); +} + +static int etm_trace_id_simple(struct etm_drvdata *drvdata) +{ + if (!drvdata->enable) + return drvdata->traceid; + + return (etm_readl(drvdata, ETMTRACEIDR) & ETM_TRACEID_MASK); +} + +static int etm_trace_id(struct coresight_device *csdev) +{ + struct etm_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); + unsigned long flags; + int trace_id = -1; + + if (!drvdata->enable) + return drvdata->traceid; + + if (clk_prepare_enable(drvdata->clk)) + goto out; + + spin_lock_irqsave(&drvdata->spinlock, flags); + + CS_UNLOCK(drvdata->base); + trace_id = (etm_readl(drvdata, ETMTRACEIDR) & ETM_TRACEID_MASK); + CS_LOCK(drvdata->base); + + spin_unlock_irqrestore(&drvdata->spinlock, flags); + clk_disable_unprepare(drvdata->clk); +out: + return trace_id; +} + +static int etm_enable(struct coresight_device *csdev) +{ + struct etm_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); + int ret; + + ret = clk_prepare_enable(drvdata->clk); + if (ret) + goto err_clk; + + spin_lock(&drvdata->spinlock); + + /* + * Configure the ETM only if the CPU is online. If it isn't online + * hw configuration will take place when 'CPU_STARTING' is received + * in @etm_cpu_callback. + */ + if (cpu_online(drvdata->cpu)) { + ret = smp_call_function_single(drvdata->cpu, + etm_enable_hw, drvdata, 1); + if (ret) + goto err; + } + + drvdata->enable = true; + drvdata->sticky_enable = true; + + spin_unlock(&drvdata->spinlock); + + dev_info(drvdata->dev, "ETM tracing enabled\n"); + return 0; +err: + spin_unlock(&drvdata->spinlock); + clk_disable_unprepare(drvdata->clk); +err_clk: + return ret; +} + +static void etm_disable_hw(void *info) +{ + int i; + struct etm_drvdata *drvdata = info; + + CS_UNLOCK(drvdata->base); + etm_set_prog(drvdata); + + /* Program trace enable to low by using always false event */ + etm_writel(drvdata, ETM_HARD_WIRE_RES_A | ETM_EVENT_NOT_A, ETMTEEVR); + + /* Read back sequencer and counters for post trace analysis */ + drvdata->seq_curr_state = (etm_readl(drvdata, ETMSQR) & ETM_SQR_MASK); + + for (i = 0; i < drvdata->nr_cntr; i++) + drvdata->cntr_val[i] = etm_readl(drvdata, ETMCNTVRn(i)); + + etm_set_pwrdwn(drvdata); + CS_LOCK(drvdata->base); + + dev_dbg(drvdata->dev, "cpu: %d disable smp call done\n", drvdata->cpu); +} + +static void etm_disable(struct coresight_device *csdev) +{ + struct etm_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); + + /* + * Taking hotplug lock here protects from clocks getting disabled + * with tracing being left on (crash scenario) if user disable occurs + * after cpu online mask indicates the cpu is offline but before the + * DYING hotplug callback is serviced by the ETM driver. + */ + get_online_cpus(); + spin_lock(&drvdata->spinlock); + + /* + * Executing etm_disable_hw on the cpu whose ETM is being disabled + * ensures that register writes occur when cpu is powered. + */ + smp_call_function_single(drvdata->cpu, etm_disable_hw, drvdata, 1); + drvdata->enable = false; + + spin_unlock(&drvdata->spinlock); + put_online_cpus(); + + clk_disable_unprepare(drvdata->clk); + + dev_info(drvdata->dev, "ETM tracing disabled\n"); +} + +static const struct coresight_ops_source etm_source_ops = { + .trace_id = etm_trace_id, + .enable = etm_enable, + .disable = etm_disable, +}; + +static const struct coresight_ops etm_cs_ops = { + .source_ops = &etm_source_ops, +}; + +static ssize_t nr_addr_cmp_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned long val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + val = drvdata->nr_addr_cmp; + return sprintf(buf, "%#lx\n", val); +} +static DEVICE_ATTR_RO(nr_addr_cmp); + +static ssize_t nr_cntr_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ unsigned long val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + val = drvdata->nr_cntr; + return sprintf(buf, "%#lx\n", val); +} +static DEVICE_ATTR_RO(nr_cntr); + +static ssize_t nr_ctxid_cmp_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned long val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + val = drvdata->nr_ctxid_cmp; + return sprintf(buf, "%#lx\n", val); +} +static DEVICE_ATTR_RO(nr_ctxid_cmp); + +static ssize_t etmsr_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret; + unsigned long flags, val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + ret = clk_prepare_enable(drvdata->clk); + if (ret) + return ret; + + spin_lock_irqsave(&drvdata->spinlock, flags); + CS_UNLOCK(drvdata->base); + + val = etm_readl(drvdata, ETMSR); + + CS_LOCK(drvdata->base); + spin_unlock_irqrestore(&drvdata->spinlock, flags); + clk_disable_unprepare(drvdata->clk); + + return sprintf(buf, "%#lx\n", val); +} +static DEVICE_ATTR_RO(etmsr); + +static ssize_t reset_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int i, ret; + unsigned long val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + ret = kstrtoul(buf, 16, &val); + if (ret) + return ret; + + if (val) { + spin_lock(&drvdata->spinlock); + drvdata->mode = ETM_MODE_EXCLUDE; + drvdata->ctrl = 0x0; + drvdata->trigger_event = ETM_DEFAULT_EVENT_VAL; + drvdata->startstop_ctrl = 0x0; + drvdata->addr_idx = 0x0; + for (i = 0; i < drvdata->nr_addr_cmp; i++) { + drvdata->addr_val[i] = 0x0; + drvdata->addr_acctype[i] = 0x0; + drvdata->addr_type[i] = ETM_ADDR_TYPE_NONE; + } + drvdata->cntr_idx = 0x0; + + etm_set_default(drvdata); + spin_unlock(&drvdata->spinlock); + } + + return size; +} +static DEVICE_ATTR_WO(reset); + +static ssize_t mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned long val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + val = drvdata->mode; + return sprintf(buf, "%#lx\n", val); +} + +static ssize_t mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int ret; + unsigned long val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + ret = kstrtoul(buf, 16, &val); + if (ret) + return ret; + + spin_lock(&drvdata->spinlock); + drvdata->mode = val & ETM_MODE_ALL; + + if (drvdata->mode & ETM_MODE_EXCLUDE) + drvdata->enable_ctrl1 |= ETMTECR1_INC_EXC; + else + drvdata->enable_ctrl1 &= ~ETMTECR1_INC_EXC; + + if (drvdata->mode & ETM_MODE_CYCACC) + drvdata->ctrl |= ETMCR_CYC_ACC; + else + drvdata->ctrl &= ~ETMCR_CYC_ACC; + + if (drvdata->mode & ETM_MODE_STALL) { + if (!(drvdata->etmccr & ETMCCR_FIFOFULL)) { + dev_warn(drvdata->dev, "stall mode not supported\n"); + ret = -EINVAL; + goto err_unlock; + } + drvdata->ctrl |= ETMCR_STALL_MODE; + } else + drvdata->ctrl &= ~ETMCR_STALL_MODE; + + if (drvdata->mode & ETM_MODE_TIMESTAMP) { + if (!(drvdata->etmccer & ETMCCER_TIMESTAMP)) { + dev_warn(drvdata->dev, "timestamp not supported\n"); + ret = -EINVAL; + goto err_unlock; + } + drvdata->ctrl |= ETMCR_TIMESTAMP_EN; + } else + drvdata->ctrl &= ~ETMCR_TIMESTAMP_EN; + + if (drvdata->mode & ETM_MODE_CTXID) + drvdata->ctrl |= ETMCR_CTXID_SIZE; + else + drvdata->ctrl &= ~ETMCR_CTXID_SIZE; + spin_unlock(&drvdata->spinlock); + + return size; + +err_unlock: + spin_unlock(&drvdata->spinlock); + return ret; +} +static DEVICE_ATTR_RW(mode); + +static ssize_t trigger_event_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned long val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + val = drvdata->trigger_event; + return sprintf(buf, "%#lx\n", val); +} + +static ssize_t trigger_event_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int ret; + unsigned long val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + ret = kstrtoul(buf, 16, &val); + if (ret) + return ret; + + drvdata->trigger_event = val & ETM_EVENT_MASK; + + return size; +} +static DEVICE_ATTR_RW(trigger_event); + +static ssize_t enable_event_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned long val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + val = drvdata->enable_event; + return sprintf(buf, "%#lx\n", val); +} + +static ssize_t enable_event_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int ret; + unsigned long val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + ret = kstrtoul(buf, 16, &val); + if (ret) + return ret; + + drvdata->enable_event = val & ETM_EVENT_MASK; + + return size; +} +static DEVICE_ATTR_RW(enable_event); + +static ssize_t fifofull_level_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned long val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + val = drvdata->fifofull_level; + return sprintf(buf, "%#lx\n", val); +} + +static ssize_t fifofull_level_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int ret; + unsigned long val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + ret = kstrtoul(buf, 16, &val); + if (ret) + return ret; + + drvdata->fifofull_level = val; + + return size; +} +static DEVICE_ATTR_RW(fifofull_level); + +static ssize_t addr_idx_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned long val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + val = drvdata->addr_idx; + return sprintf(buf, "%#lx\n", val); +} + +static ssize_t addr_idx_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int ret; + unsigned long val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + ret = kstrtoul(buf, 16, &val); + if (ret) + return ret; + + if (val >= drvdata->nr_addr_cmp) + return -EINVAL; + + /* + * Use spinlock to ensure index doesn't change while it gets + * dereferenced multiple times within a spinlock block elsewhere. + */ + spin_lock(&drvdata->spinlock); + drvdata->addr_idx = val; + spin_unlock(&drvdata->spinlock); + + return size; +} +static DEVICE_ATTR_RW(addr_idx); + +static ssize_t addr_single_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + u8 idx; + unsigned long val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + spin_lock(&drvdata->spinlock); + idx = drvdata->addr_idx; + if (!(drvdata->addr_type[idx] == ETM_ADDR_TYPE_NONE || + drvdata->addr_type[idx] == ETM_ADDR_TYPE_SINGLE)) { + spin_unlock(&drvdata->spinlock); + return -EINVAL; + } + + val = drvdata->addr_val[idx]; + spin_unlock(&drvdata->spinlock); + + return sprintf(buf, "%#lx\n", val); +} + +static ssize_t addr_single_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + u8 idx; + int ret; + unsigned long val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + ret = kstrtoul(buf, 16, &val); + if (ret) + return ret; + + spin_lock(&drvdata->spinlock); + idx = drvdata->addr_idx; + if (!(drvdata->addr_type[idx] == ETM_ADDR_TYPE_NONE || + drvdata->addr_type[idx] == ETM_ADDR_TYPE_SINGLE)) { + spin_unlock(&drvdata->spinlock); + return -EINVAL; + } + + drvdata->addr_val[idx] = val; + drvdata->addr_type[idx] = ETM_ADDR_TYPE_SINGLE; + spin_unlock(&drvdata->spinlock); + + return size; +} +static DEVICE_ATTR_RW(addr_single); + +static ssize_t addr_range_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + u8 idx; + unsigned long val1, val2; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + spin_lock(&drvdata->spinlock); + idx = drvdata->addr_idx; + if (idx % 2 != 0) { + spin_unlock(&drvdata->spinlock); + return -EPERM; + } + if (!((drvdata->addr_type[idx] == ETM_ADDR_TYPE_NONE && + drvdata->addr_type[idx + 1] == ETM_ADDR_TYPE_NONE) || + (drvdata->addr_type[idx] == ETM_ADDR_TYPE_RANGE && + drvdata->addr_type[idx + 1] == ETM_ADDR_TYPE_RANGE))) { + spin_unlock(&drvdata->spinlock); + return -EPERM; + } + + val1 = drvdata->addr_val[idx]; + val2 = drvdata->addr_val[idx + 1]; + spin_unlock(&drvdata->spinlock); + + return sprintf(buf, "%#lx %#lx\n", val1, val2); +} + +static ssize_t addr_range_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + u8 idx; + unsigned long val1, val2; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + if (sscanf(buf, "%lx %lx", &val1, &val2) != 2) + return -EINVAL; + /* Lower address comparator cannot have a higher address value */ + if (val1 > val2) + return -EINVAL; + + spin_lock(&drvdata->spinlock); + idx = drvdata->addr_idx; + if (idx % 2 != 0) { + spin_unlock(&drvdata->spinlock); + return -EPERM; + } + if (!((drvdata->addr_type[idx] == ETM_ADDR_TYPE_NONE && + drvdata->addr_type[idx + 1] == ETM_ADDR_TYPE_NONE) || + (drvdata->addr_type[idx] == ETM_ADDR_TYPE_RANGE && + drvdata->addr_type[idx + 1] == ETM_ADDR_TYPE_RANGE))) { + spin_unlock(&drvdata->spinlock); + return -EPERM; + } + + drvdata->addr_val[idx] = val1; + drvdata->addr_type[idx] = ETM_ADDR_TYPE_RANGE; + drvdata->addr_val[idx + 1] = val2; + drvdata->addr_type[idx + 1] = ETM_ADDR_TYPE_RANGE; + drvdata->enable_ctrl1 |= (1 << (idx/2)); + spin_unlock(&drvdata->spinlock); + + return size; +} +static DEVICE_ATTR_RW(addr_range); + +static ssize_t addr_start_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + u8 idx; + unsigned long val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + spin_lock(&drvdata->spinlock); + idx = drvdata->addr_idx; + if (!(drvdata->addr_type[idx] == ETM_ADDR_TYPE_NONE || + drvdata->addr_type[idx] == ETM_ADDR_TYPE_START)) { + spin_unlock(&drvdata->spinlock); + return -EPERM; + } + + val = drvdata->addr_val[idx]; + spin_unlock(&drvdata->spinlock); + + return sprintf(buf, "%#lx\n", val); +} + +static ssize_t addr_start_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + u8 idx; + int ret; + unsigned long val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + ret = kstrtoul(buf, 16, &val); + if (ret) + return ret; + + spin_lock(&drvdata->spinlock); + idx = drvdata->addr_idx; + if (!(drvdata->addr_type[idx] == ETM_ADDR_TYPE_NONE || + drvdata->addr_type[idx] == ETM_ADDR_TYPE_START)) { + spin_unlock(&drvdata->spinlock); + return -EPERM; + } + + drvdata->addr_val[idx] = val; + drvdata->addr_type[idx] = ETM_ADDR_TYPE_START; + drvdata->startstop_ctrl |= (1 << idx); + drvdata->enable_ctrl1 |= BIT(25); + spin_unlock(&drvdata->spinlock); + + return size; +} +static DEVICE_ATTR_RW(addr_start); + +static ssize_t addr_stop_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + u8 idx; + unsigned long val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + spin_lock(&drvdata->spinlock); + idx = drvdata->addr_idx; + if (!(drvdata->addr_type[idx] == ETM_ADDR_TYPE_NONE || + drvdata->addr_type[idx] == ETM_ADDR_TYPE_STOP)) { + spin_unlock(&drvdata->spinlock); + return -EPERM; + } + + val = drvdata->addr_val[idx]; + spin_unlock(&drvdata->spinlock); + + return sprintf(buf, "%#lx\n", val); +} + +static ssize_t addr_stop_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + u8 idx; + int ret; + unsigned long val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + ret = kstrtoul(buf, 16, &val); + if (ret) + return ret; + + spin_lock(&drvdata->spinlock); + idx = drvdata->addr_idx; + if (!(drvdata->addr_type[idx] == ETM_ADDR_TYPE_NONE || + drvdata->addr_type[idx] == ETM_ADDR_TYPE_STOP)) { + spin_unlock(&drvdata->spinlock); + return -EPERM; + } + + drvdata->addr_val[idx] = val; + drvdata->addr_type[idx] = ETM_ADDR_TYPE_STOP; + drvdata->startstop_ctrl |= (1 << (idx + 16)); + drvdata->enable_ctrl1 |= ETMTECR1_START_STOP; + spin_unlock(&drvdata->spinlock); + + return size; +} +static DEVICE_ATTR_RW(addr_stop); + +static ssize_t addr_acctype_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned long val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + spin_lock(&drvdata->spinlock); + val = drvdata->addr_acctype[drvdata->addr_idx]; + spin_unlock(&drvdata->spinlock); + + return sprintf(buf, "%#lx\n", val); +} + +static ssize_t addr_acctype_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int ret; + unsigned long val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + ret = kstrtoul(buf, 16, &val); + if (ret) + return ret; + + spin_lock(&drvdata->spinlock); + drvdata->addr_acctype[drvdata->addr_idx] = val; + spin_unlock(&drvdata->spinlock); + + return size; +} +static DEVICE_ATTR_RW(addr_acctype); + +static ssize_t cntr_idx_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned long val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + val = drvdata->cntr_idx; + return sprintf(buf, "%#lx\n", val); +} + +static ssize_t cntr_idx_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int ret; + unsigned long val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + ret = kstrtoul(buf, 16, &val); + if (ret) + return ret; + + if (val >= drvdata->nr_cntr) + return -EINVAL; + /* + * Use spinlock to ensure index doesn't change while it gets + * dereferenced multiple times within a spinlock block elsewhere. + */ + spin_lock(&drvdata->spinlock); + drvdata->cntr_idx = val; + spin_unlock(&drvdata->spinlock); + + return size; +} +static DEVICE_ATTR_RW(cntr_idx); + +static ssize_t cntr_rld_val_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned long val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + spin_lock(&drvdata->spinlock); + val = drvdata->cntr_rld_val[drvdata->cntr_idx]; + spin_unlock(&drvdata->spinlock); + + return sprintf(buf, "%#lx\n", val); +} + +static ssize_t cntr_rld_val_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int ret; + unsigned long val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + ret = kstrtoul(buf, 16, &val); + if (ret) + return ret; + + spin_lock(&drvdata->spinlock); + drvdata->cntr_rld_val[drvdata->cntr_idx] = val; + spin_unlock(&drvdata->spinlock); + + return size; +} +static DEVICE_ATTR_RW(cntr_rld_val); + +static ssize_t cntr_event_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned long val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + spin_lock(&drvdata->spinlock); + val = drvdata->cntr_event[drvdata->cntr_idx]; + spin_unlock(&drvdata->spinlock); + + return sprintf(buf, "%#lx\n", val); +} + +static ssize_t cntr_event_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int ret; + unsigned long val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + ret = kstrtoul(buf, 16, &val); + if (ret) + return ret; + + spin_lock(&drvdata->spinlock); + drvdata->cntr_event[drvdata->cntr_idx] = val & ETM_EVENT_MASK; + spin_unlock(&drvdata->spinlock); + + return size; +} +static DEVICE_ATTR_RW(cntr_event); + +static ssize_t cntr_rld_event_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned long val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + spin_lock(&drvdata->spinlock); + val = drvdata->cntr_rld_event[drvdata->cntr_idx]; + spin_unlock(&drvdata->spinlock); + + return sprintf(buf, "%#lx\n", val); +} + +static ssize_t cntr_rld_event_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int ret; + unsigned long val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + ret = kstrtoul(buf, 16, &val); + if (ret) + return ret; + + spin_lock(&drvdata->spinlock); + drvdata->cntr_rld_event[drvdata->cntr_idx] = val & ETM_EVENT_MASK; + spin_unlock(&drvdata->spinlock); + + return size; +} +static DEVICE_ATTR_RW(cntr_rld_event); + +static ssize_t cntr_val_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i, ret = 0; + u32 val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + if (!drvdata->enable) { + spin_lock(&drvdata->spinlock); + for (i = 0; i < drvdata->nr_cntr; i++) + ret += sprintf(buf, "counter %d: %x\n", + i, drvdata->cntr_val[i]); + spin_unlock(&drvdata->spinlock); + return ret; + } + + for (i = 0; i < drvdata->nr_cntr; i++) { + val = etm_readl(drvdata, ETMCNTVRn(i)); + ret += sprintf(buf, "counter %d: %x\n", i, val); + } + + return ret; +} + +static ssize_t cntr_val_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int ret; + unsigned long val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + ret = kstrtoul(buf, 16, &val); + if (ret) + return ret; + + spin_lock(&drvdata->spinlock); + drvdata->cntr_val[drvdata->cntr_idx] = val; + spin_unlock(&drvdata->spinlock); + + return size; +} +static DEVICE_ATTR_RW(cntr_val); + +static ssize_t seq_12_event_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned long val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + val = drvdata->seq_12_event; + return sprintf(buf, "%#lx\n", val); +} + +static ssize_t seq_12_event_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int ret; + unsigned long val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + ret = kstrtoul(buf, 16, &val); + if (ret) + return ret; + + drvdata->seq_12_event = val & ETM_EVENT_MASK; + return size; +} +static DEVICE_ATTR_RW(seq_12_event); + +static ssize_t seq_21_event_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned long val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + val = drvdata->seq_21_event; + return sprintf(buf, "%#lx\n", val); +} + +static ssize_t seq_21_event_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int ret; + unsigned long val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + ret = kstrtoul(buf, 16, &val); + if (ret) + return ret; + + drvdata->seq_21_event = val & ETM_EVENT_MASK; + return size; +} +static DEVICE_ATTR_RW(seq_21_event); + +static ssize_t seq_23_event_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned long val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + val = drvdata->seq_23_event; + return sprintf(buf, "%#lx\n", val); +} + +static ssize_t seq_23_event_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int ret; + unsigned long val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + ret = kstrtoul(buf, 16, &val); + if (ret) + return ret; + + drvdata->seq_23_event = val & ETM_EVENT_MASK; + return size; +} +static DEVICE_ATTR_RW(seq_23_event); + +static ssize_t seq_31_event_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned long val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + val = drvdata->seq_31_event; + return sprintf(buf, "%#lx\n", val); +} + +static ssize_t seq_31_event_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int ret; + unsigned long val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + ret = kstrtoul(buf, 16, &val); + if (ret) + return ret; + + drvdata->seq_31_event = val & ETM_EVENT_MASK; + return size; +} +static DEVICE_ATTR_RW(seq_31_event); + +static ssize_t seq_32_event_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned long val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + val = drvdata->seq_32_event; + return sprintf(buf, "%#lx\n", val); +} + +static ssize_t seq_32_event_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int ret; + unsigned long val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + ret = kstrtoul(buf, 16, &val); + if (ret) + return ret; + + drvdata->seq_32_event = val & ETM_EVENT_MASK; + return size; +} +static DEVICE_ATTR_RW(seq_32_event); + +static ssize_t seq_13_event_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned long val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + val = drvdata->seq_13_event; + return sprintf(buf, "%#lx\n", val); +} + +static ssize_t seq_13_event_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int ret; + unsigned long val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + ret = kstrtoul(buf, 16, &val); + if (ret) + return ret; + + drvdata->seq_13_event = val & ETM_EVENT_MASK; + return size; +} +static DEVICE_ATTR_RW(seq_13_event); + +static ssize_t seq_curr_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret; + unsigned long val, flags; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + if (!drvdata->enable) { + val = drvdata->seq_curr_state; + goto out; + } + + ret = clk_prepare_enable(drvdata->clk); + if (ret) + return ret; + + spin_lock_irqsave(&drvdata->spinlock, flags); + + CS_UNLOCK(drvdata->base); + val = (etm_readl(drvdata, ETMSQR) & ETM_SQR_MASK); + CS_LOCK(drvdata->base); + + spin_unlock_irqrestore(&drvdata->spinlock, flags); + clk_disable_unprepare(drvdata->clk); +out: + return sprintf(buf, "%#lx\n", val); +} + +static ssize_t seq_curr_state_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int ret; + unsigned long val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + ret = kstrtoul(buf, 16, &val); + if (ret) + return ret; + + if (val > ETM_SEQ_STATE_MAX_VAL) + return -EINVAL; + + drvdata->seq_curr_state = val; + + return size; +} +static DEVICE_ATTR_RW(seq_curr_state); + +static ssize_t ctxid_idx_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned long val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + val = drvdata->ctxid_idx; + return sprintf(buf, "%#lx\n", val); +} + +static ssize_t ctxid_idx_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int ret; + unsigned long val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + ret = kstrtoul(buf, 16, &val); + if (ret) + return ret; + + if (val >= drvdata->nr_ctxid_cmp) + return -EINVAL; + + /* + * Use spinlock to ensure index doesn't change while it gets + * dereferenced multiple times within a spinlock block elsewhere. + */ + spin_lock(&drvdata->spinlock); + drvdata->ctxid_idx = val; + spin_unlock(&drvdata->spinlock); + + return size; +} +static DEVICE_ATTR_RW(ctxid_idx); + +static ssize_t ctxid_val_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned long val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + spin_lock(&drvdata->spinlock); + val = drvdata->ctxid_val[drvdata->ctxid_idx]; + spin_unlock(&drvdata->spinlock); + + return sprintf(buf, "%#lx\n", val); +} + +static ssize_t ctxid_val_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int ret; + unsigned long val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + ret = kstrtoul(buf, 16, &val); + if (ret) + return ret; + + spin_lock(&drvdata->spinlock); + drvdata->ctxid_val[drvdata->ctxid_idx] = val; + spin_unlock(&drvdata->spinlock); + + return size; +} +static DEVICE_ATTR_RW(ctxid_val); + +static ssize_t ctxid_mask_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned long val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + val = drvdata->ctxid_mask; + return sprintf(buf, "%#lx\n", val); +} + +static ssize_t ctxid_mask_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int ret; + unsigned long val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + ret = kstrtoul(buf, 16, &val); + if (ret) + return ret; + + drvdata->ctxid_mask = val; + return size; +} +static DEVICE_ATTR_RW(ctxid_mask); + +static ssize_t sync_freq_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned long val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + val = drvdata->sync_freq; + return sprintf(buf, "%#lx\n", val); +} + +static ssize_t sync_freq_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int ret; + unsigned long val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + ret = kstrtoul(buf, 16, &val); + if (ret) + return ret; + + drvdata->sync_freq = val & ETM_SYNC_MASK; + return size; +} +static DEVICE_ATTR_RW(sync_freq); + +static ssize_t timestamp_event_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned long val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + val = drvdata->timestamp_event; + return sprintf(buf, "%#lx\n", val); +} + +static ssize_t timestamp_event_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int ret; + unsigned long val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + ret = kstrtoul(buf, 16, &val); + if (ret) + return ret; + + drvdata->timestamp_event = val & ETM_EVENT_MASK; + return size; +} +static DEVICE_ATTR_RW(timestamp_event); + +static ssize_t status_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret; + unsigned long flags; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + ret = clk_prepare_enable(drvdata->clk); + if (ret) + return ret; + + spin_lock_irqsave(&drvdata->spinlock, flags); + + CS_UNLOCK(drvdata->base); + ret = sprintf(buf, + "ETMCCR: 0x%08x\n" + "ETMCCER: 0x%08x\n" + "ETMSCR: 0x%08x\n" + "ETMIDR: 0x%08x\n" + "ETMCR: 0x%08x\n" + "ETMTRACEIDR: 0x%08x\n" + "Enable event: 0x%08x\n" + "Enable start/stop: 0x%08x\n" + "Enable control: CR1 0x%08x CR2 0x%08x\n" + "CPU affinity: %d\n", + drvdata->etmccr, drvdata->etmccer, + etm_readl(drvdata, ETMSCR), etm_readl(drvdata, ETMIDR), + etm_readl(drvdata, ETMCR), etm_trace_id_simple(drvdata), + etm_readl(drvdata, ETMTEEVR), + etm_readl(drvdata, ETMTSSCR), + etm_readl(drvdata, ETMTECR1), + etm_readl(drvdata, ETMTECR2), + drvdata->cpu); + CS_LOCK(drvdata->base); + + spin_unlock_irqrestore(&drvdata->spinlock, flags); + clk_disable_unprepare(drvdata->clk); + + return ret; +} +static DEVICE_ATTR_RO(status); + +static ssize_t traceid_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret; + unsigned long val, flags; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + if (!drvdata->enable) { + val = drvdata->traceid; + goto out; + } + + ret = clk_prepare_enable(drvdata->clk); + if (ret) + return ret; + + spin_lock_irqsave(&drvdata->spinlock, flags); + CS_UNLOCK(drvdata->base); + + val = (etm_readl(drvdata, ETMTRACEIDR) & ETM_TRACEID_MASK); + + CS_LOCK(drvdata->base); + spin_unlock_irqrestore(&drvdata->spinlock, flags); + clk_disable_unprepare(drvdata->clk); +out: + return sprintf(buf, "%#lx\n", val); +} + +static ssize_t traceid_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int ret; + unsigned long val; + struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + ret = kstrtoul(buf, 16, &val); + if (ret) + return ret; + + drvdata->traceid = val & ETM_TRACEID_MASK; + return size; +} +static DEVICE_ATTR_RW(traceid); + +static struct attribute *coresight_etm_attrs[] = { + &dev_attr_nr_addr_cmp.attr, + &dev_attr_nr_cntr.attr, + &dev_attr_nr_ctxid_cmp.attr, + &dev_attr_etmsr.attr, + &dev_attr_reset.attr, + &dev_attr_mode.attr, + &dev_attr_trigger_event.attr, + &dev_attr_enable_event.attr, + &dev_attr_fifofull_level.attr, + &dev_attr_addr_idx.attr, + &dev_attr_addr_single.attr, + &dev_attr_addr_range.attr, + &dev_attr_addr_start.attr, + &dev_attr_addr_stop.attr, + &dev_attr_addr_acctype.attr, + &dev_attr_cntr_idx.attr, + &dev_attr_cntr_rld_val.attr, + &dev_attr_cntr_event.attr, + &dev_attr_cntr_rld_event.attr, + &dev_attr_cntr_val.attr, + &dev_attr_seq_12_event.attr, + &dev_attr_seq_21_event.attr, + &dev_attr_seq_23_event.attr, + &dev_attr_seq_31_event.attr, + &dev_attr_seq_32_event.attr, + &dev_attr_seq_13_event.attr, + &dev_attr_seq_curr_state.attr, + &dev_attr_ctxid_idx.attr, + &dev_attr_ctxid_val.attr, + &dev_attr_ctxid_mask.attr, + &dev_attr_sync_freq.attr, + &dev_attr_timestamp_event.attr, + &dev_attr_status.attr, + &dev_attr_traceid.attr, + NULL, +}; +ATTRIBUTE_GROUPS(coresight_etm); + +static int etm_cpu_callback(struct notifier_block *nfb, unsigned long action, + void *hcpu) +{ + unsigned int cpu = (unsigned long)hcpu; + + if (!etmdrvdata[cpu]) + goto out; + + switch (action & (~CPU_TASKS_FROZEN)) { + case CPU_STARTING: + spin_lock(&etmdrvdata[cpu]->spinlock); + if (!etmdrvdata[cpu]->os_unlock) { + etm_os_unlock(etmdrvdata[cpu]); + etmdrvdata[cpu]->os_unlock = true; + } + + if (etmdrvdata[cpu]->enable) + etm_enable_hw(etmdrvdata[cpu]); + spin_unlock(&etmdrvdata[cpu]->spinlock); + break; + + case CPU_ONLINE: + if (etmdrvdata[cpu]->boot_enable && + !etmdrvdata[cpu]->sticky_enable) + coresight_enable(etmdrvdata[cpu]->csdev); + break; + + case CPU_DYING: + spin_lock(&etmdrvdata[cpu]->spinlock); + if (etmdrvdata[cpu]->enable) + etm_disable_hw(etmdrvdata[cpu]); + spin_unlock(&etmdrvdata[cpu]->spinlock); + break; + } +out: + return NOTIFY_OK; +} + +static struct notifier_block etm_cpu_notifier = { + .notifier_call = etm_cpu_callback, +}; + +static bool etm_arch_supported(u8 arch) +{ + switch (arch) { + case ETM_ARCH_V3_3: + break; + case ETM_ARCH_V3_5: + break; + case PFT_ARCH_V1_0: + break; + case PFT_ARCH_V1_1: + break; + default: + return false; + } + return true; +} + +static void etm_init_arch_data(void *info) +{ + u32 etmidr; + u32 etmccr; + struct etm_drvdata *drvdata = info; + + CS_UNLOCK(drvdata->base); + + /* First dummy read */ + (void)etm_readl(drvdata, ETMPDSR); + /* Provide power to ETM: ETMPDCR[3] == 1 */ + etm_set_pwrup(drvdata); + /* + * Clear power down bit since when this bit is set writes to + * certain registers might be ignored. + */ + etm_clr_pwrdwn(drvdata); + /* + * Set prog bit. It will be set from reset but this is included to + * ensure it is set + */ + etm_set_prog(drvdata); + + /* Find all capabilities */ + etmidr = etm_readl(drvdata, ETMIDR); + drvdata->arch = BMVAL(etmidr, 4, 11); + drvdata->port_size = etm_readl(drvdata, ETMCR) & PORT_SIZE_MASK; + + drvdata->etmccer = etm_readl(drvdata, ETMCCER); + etmccr = etm_readl(drvdata, ETMCCR); + drvdata->etmccr = etmccr; + drvdata->nr_addr_cmp = BMVAL(etmccr, 0, 3) * 2; + drvdata->nr_cntr = BMVAL(etmccr, 13, 15); + drvdata->nr_ext_inp = BMVAL(etmccr, 17, 19); + drvdata->nr_ext_out = BMVAL(etmccr, 20, 22); + drvdata->nr_ctxid_cmp = BMVAL(etmccr, 24, 25); + + etm_set_pwrdwn(drvdata); + etm_clr_pwrup(drvdata); + CS_LOCK(drvdata->base); +} + +static void etm_init_default_data(struct etm_drvdata *drvdata) +{ + /* + * A trace ID of value 0 is invalid, so let's start at some + * random value that fits in 7 bits and will be just as good. + */ + static int etm3x_traceid = 0x10; + + u32 flags = (1 << 0 | /* instruction execute*/ + 3 << 3 | /* ARM instruction */ + 0 << 5 | /* No data value comparison */ + 0 << 7 | /* No exact mach */ + 0 << 8 | /* Ignore context ID */ + 0 << 10); /* Security ignored */ + + /* + * Initial configuration only - guarantees sources handled by + * this driver have a unique ID at startup time but not between + * all other types of sources. For that we lean on the core + * framework. + */ + drvdata->traceid = etm3x_traceid++; + drvdata->ctrl = (ETMCR_CYC_ACC | ETMCR_TIMESTAMP_EN); + drvdata->enable_ctrl1 = ETMTECR1_ADDR_COMP_1; + if (drvdata->nr_addr_cmp >= 2) { + drvdata->addr_val[0] = (u32) _stext; + drvdata->addr_val[1] = (u32) _etext; + drvdata->addr_acctype[0] = flags; + drvdata->addr_acctype[1] = flags; + drvdata->addr_type[0] = ETM_ADDR_TYPE_RANGE; + drvdata->addr_type[1] = ETM_ADDR_TYPE_RANGE; + } + + etm_set_default(drvdata); +} + +static int etm_probe(struct amba_device *adev, const struct amba_id *id) +{ + int ret; + void __iomem *base; + struct device *dev = &adev->dev; + struct coresight_platform_data *pdata = NULL; + struct etm_drvdata *drvdata; + struct resource *res = &adev->res; + struct coresight_desc *desc; + struct device_node *np = adev->dev.of_node; + + desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL); + if (!desc) + return -ENOMEM; + + drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + if (np) { + pdata = of_get_coresight_platform_data(dev, np); + if (IS_ERR(pdata)) + return PTR_ERR(pdata); + + adev->dev.platform_data = pdata; + drvdata->use_cp14 = of_property_read_bool(np, "arm,cp14"); + } + + drvdata->dev = &adev->dev; + dev_set_drvdata(dev, drvdata); + + /* Validity for the resource is already checked by the AMBA core */ + base = devm_ioremap_resource(dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + drvdata->base = base; + + spin_lock_init(&drvdata->spinlock); + + drvdata->clk = adev->pclk; + ret = clk_prepare_enable(drvdata->clk); + if (ret) + return ret; + + drvdata->cpu = pdata ? pdata->cpu : 0; + + get_online_cpus(); + etmdrvdata[drvdata->cpu] = drvdata; + + if (!smp_call_function_single(drvdata->cpu, etm_os_unlock, drvdata, 1)) + drvdata->os_unlock = true; + + if (smp_call_function_single(drvdata->cpu, + etm_init_arch_data, drvdata, 1)) + dev_err(dev, "ETM arch init failed\n"); + + if (!etm_count++) + register_hotcpu_notifier(&etm_cpu_notifier); + + put_online_cpus(); + + if (etm_arch_supported(drvdata->arch) == false) { + ret = -EINVAL; + goto err_arch_supported; + } + etm_init_default_data(drvdata); + + clk_disable_unprepare(drvdata->clk); + + desc->type = CORESIGHT_DEV_TYPE_SOURCE; + desc->subtype.source_subtype = CORESIGHT_DEV_SUBTYPE_SOURCE_PROC; + desc->ops = &etm_cs_ops; + desc->pdata = pdata; + desc->dev = dev; + desc->groups = coresight_etm_groups; + drvdata->csdev = coresight_register(desc); + if (IS_ERR(drvdata->csdev)) { + ret = PTR_ERR(drvdata->csdev); + goto err_arch_supported; + } + + dev_info(dev, "ETM initialized\n"); + + if (boot_enable) { + coresight_enable(drvdata->csdev); + drvdata->boot_enable = true; + } + + return 0; + +err_arch_supported: + clk_disable_unprepare(drvdata->clk); + if (--etm_count == 0) + unregister_hotcpu_notifier(&etm_cpu_notifier); + return ret; +} + +static int etm_remove(struct amba_device *adev) +{ + struct etm_drvdata *drvdata = amba_get_drvdata(adev); + + coresight_unregister(drvdata->csdev); + if (--etm_count == 0) + unregister_hotcpu_notifier(&etm_cpu_notifier); + + return 0; +} + +static struct amba_id etm_ids[] = { + { /* ETM 3.3 */ + .id = 0x0003b921, + .mask = 0x0003ffff, + }, + { /* ETM 3.5 */ + .id = 0x0003b956, + .mask = 0x0003ffff, + }, + { /* PTM 1.0 */ + .id = 0x0003b950, + .mask = 0x0003ffff, + }, + { /* PTM 1.1 */ + .id = 0x0003b95f, + .mask = 0x0003ffff, + }, + { 0, 0}, +}; + +static struct amba_driver etm_driver = { + .drv = { + .name = "coresight-etm3x", + .owner = THIS_MODULE, + }, + .probe = etm_probe, + .remove = etm_remove, + .id_table = etm_ids, +}; + +int __init etm_init(void) +{ + return amba_driver_register(&etm_driver); +} +module_init(etm_init); + +void __exit etm_exit(void) +{ + amba_driver_unregister(&etm_driver); +} +module_exit(etm_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("CoreSight Program Flow Trace driver"); diff --git a/drivers/hwtracing/coresight/coresight-funnel.c b/drivers/hwtracing/coresight/coresight-funnel.c new file mode 100644 index 0000000..3db36f7 --- /dev/null +++ b/drivers/hwtracing/coresight/coresight-funnel.c @@ -0,0 +1,258 @@ +/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "coresight-priv.h" + +#define FUNNEL_FUNCTL 0x000 +#define FUNNEL_PRICTL 0x004 + +#define FUNNEL_HOLDTIME_MASK 0xf00 +#define FUNNEL_HOLDTIME_SHFT 0x8 +#define FUNNEL_HOLDTIME (0x7 << FUNNEL_HOLDTIME_SHFT) + +/** + * struct funnel_drvdata - specifics associated to a funnel component + * @base: memory mapped base address for this component. + * @dev: the device entity associated to this component. + * @csdev: component vitals needed by the framework. + * @clk: the clock this component is associated to. + * @priority: port selection order. + */ +struct funnel_drvdata { + void __iomem *base; + struct device *dev; + struct coresight_device *csdev; + struct clk *clk; + unsigned long priority; +}; + +static void funnel_enable_hw(struct funnel_drvdata *drvdata, int port) +{ + u32 functl; + + CS_UNLOCK(drvdata->base); + + functl = readl_relaxed(drvdata->base + FUNNEL_FUNCTL); + functl &= ~FUNNEL_HOLDTIME_MASK; + functl |= FUNNEL_HOLDTIME; + functl |= (1 << port); + writel_relaxed(functl, drvdata->base + FUNNEL_FUNCTL); + writel_relaxed(drvdata->priority, drvdata->base + FUNNEL_PRICTL); + + CS_LOCK(drvdata->base); +} + +static int funnel_enable(struct coresight_device *csdev, int inport, + int outport) +{ + struct funnel_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); + int ret; + + ret = clk_prepare_enable(drvdata->clk); + if (ret) + return ret; + + funnel_enable_hw(drvdata, inport); + + dev_info(drvdata->dev, "FUNNEL inport %d enabled\n", inport); + return 0; +} + +static void funnel_disable_hw(struct funnel_drvdata *drvdata, int inport) +{ + u32 functl; + + CS_UNLOCK(drvdata->base); + + functl = readl_relaxed(drvdata->base + FUNNEL_FUNCTL); + functl &= ~(1 << inport); + writel_relaxed(functl, drvdata->base + FUNNEL_FUNCTL); + + CS_LOCK(drvdata->base); +} + +static void funnel_disable(struct coresight_device *csdev, int inport, + int outport) +{ + struct funnel_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); + + funnel_disable_hw(drvdata, inport); + + clk_disable_unprepare(drvdata->clk); + + dev_info(drvdata->dev, "FUNNEL inport %d disabled\n", inport); +} + +static const struct coresight_ops_link funnel_link_ops = { + .enable = funnel_enable, + .disable = funnel_disable, +}; + +static const struct coresight_ops funnel_cs_ops = { + .link_ops = &funnel_link_ops, +}; + +static ssize_t priority_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct funnel_drvdata *drvdata = dev_get_drvdata(dev->parent); + unsigned long val = drvdata->priority; + + return sprintf(buf, "%#lx\n", val); +} + +static ssize_t priority_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int ret; + unsigned long val; + struct funnel_drvdata *drvdata = dev_get_drvdata(dev->parent); + + ret = kstrtoul(buf, 16, &val); + if (ret) + return ret; + + drvdata->priority = val; + return size; +} +static DEVICE_ATTR_RW(priority); + +static u32 get_funnel_ctrl_hw(struct funnel_drvdata *drvdata) +{ + u32 functl; + + CS_UNLOCK(drvdata->base); + functl = readl_relaxed(drvdata->base + FUNNEL_FUNCTL); + CS_LOCK(drvdata->base); + + return functl; +} + +static ssize_t funnel_ctrl_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret; + u32 val; + struct funnel_drvdata *drvdata = dev_get_drvdata(dev->parent); + + ret = clk_prepare_enable(drvdata->clk); + if (ret) + return ret; + + val = get_funnel_ctrl_hw(drvdata); + clk_disable_unprepare(drvdata->clk); + + return sprintf(buf, "%#x\n", val); +} +static DEVICE_ATTR_RO(funnel_ctrl); + +static struct attribute *coresight_funnel_attrs[] = { + &dev_attr_funnel_ctrl.attr, + &dev_attr_priority.attr, + NULL, +}; +ATTRIBUTE_GROUPS(coresight_funnel); + +static int funnel_probe(struct amba_device *adev, const struct amba_id *id) +{ + void __iomem *base; + struct device *dev = &adev->dev; + struct coresight_platform_data *pdata = NULL; + struct funnel_drvdata *drvdata; + struct resource *res = &adev->res; + struct coresight_desc *desc; + struct device_node *np = adev->dev.of_node; + + if (np) { + pdata = of_get_coresight_platform_data(dev, np); + if (IS_ERR(pdata)) + return PTR_ERR(pdata); + adev->dev.platform_data = pdata; + } + + drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + drvdata->dev = &adev->dev; + dev_set_drvdata(dev, drvdata); + + /* Validity for the resource is already checked by the AMBA core */ + base = devm_ioremap_resource(dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + drvdata->base = base; + + drvdata->clk = adev->pclk; + + desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL); + if (!desc) + return -ENOMEM; + + desc->type = CORESIGHT_DEV_TYPE_LINK; + desc->subtype.link_subtype = CORESIGHT_DEV_SUBTYPE_LINK_MERG; + desc->ops = &funnel_cs_ops; + desc->pdata = pdata; + desc->dev = dev; + desc->groups = coresight_funnel_groups; + drvdata->csdev = coresight_register(desc); + if (IS_ERR(drvdata->csdev)) + return PTR_ERR(drvdata->csdev); + + dev_info(dev, "FUNNEL initialized\n"); + return 0; +} + +static int funnel_remove(struct amba_device *adev) +{ + struct funnel_drvdata *drvdata = amba_get_drvdata(adev); + + coresight_unregister(drvdata->csdev); + return 0; +} + +static struct amba_id funnel_ids[] = { + { + .id = 0x0003b908, + .mask = 0x0003ffff, + }, + { 0, 0}, +}; + +static struct amba_driver funnel_driver = { + .drv = { + .name = "coresight-funnel", + .owner = THIS_MODULE, + }, + .probe = funnel_probe, + .remove = funnel_remove, + .id_table = funnel_ids, +}; + +module_amba_driver(funnel_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("CoreSight Funnel driver"); diff --git a/drivers/hwtracing/coresight/coresight-priv.h b/drivers/hwtracing/coresight/coresight-priv.h new file mode 100644 index 0000000..62fcd98 --- /dev/null +++ b/drivers/hwtracing/coresight/coresight-priv.h @@ -0,0 +1,63 @@ +/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _CORESIGHT_PRIV_H +#define _CORESIGHT_PRIV_H + +#include +#include +#include + +/* + * Coresight management registers (0xf00-0xfcc) + * 0xfa0 - 0xfa4: Management registers in PFTv1.0 + * Trace registers in PFTv1.1 + */ +#define CORESIGHT_ITCTRL 0xf00 +#define CORESIGHT_CLAIMSET 0xfa0 +#define CORESIGHT_CLAIMCLR 0xfa4 +#define CORESIGHT_LAR 0xfb0 +#define CORESIGHT_LSR 0xfb4 +#define CORESIGHT_AUTHSTATUS 0xfb8 +#define CORESIGHT_DEVID 0xfc8 +#define CORESIGHT_DEVTYPE 0xfcc + +#define TIMEOUT_US 100 +#define BMVAL(val, lsb, msb) ((val & GENMASK(msb, lsb)) >> lsb) + +static inline void CS_LOCK(void __iomem *addr) +{ + do { + /* Wait for things to settle */ + mb(); + writel_relaxed(0x0, addr + CORESIGHT_LAR); + } while (0); +} + +static inline void CS_UNLOCK(void __iomem *addr) +{ + do { + writel_relaxed(CORESIGHT_UNLOCK, addr + CORESIGHT_LAR); + /* Make sure everyone has seen this */ + mb(); + } while (0); +} + +#ifdef CONFIG_CORESIGHT_SOURCE_ETM3X +extern int etm_readl_cp14(u32 off, unsigned int *val); +extern int etm_writel_cp14(u32 off, u32 val); +#else +static inline int etm_readl_cp14(u32 off, unsigned int *val) { return 0; } +static inline int etm_writel_cp14(u32 off, u32 val) { return 0; } +#endif + +#endif diff --git a/drivers/hwtracing/coresight/coresight-replicator.c b/drivers/hwtracing/coresight/coresight-replicator.c new file mode 100644 index 0000000..75b9abd --- /dev/null +++ b/drivers/hwtracing/coresight/coresight-replicator.c @@ -0,0 +1,137 @@ +/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "coresight-priv.h" + +/** + * struct replicator_drvdata - specifics associated to a replicator component + * @dev: the device entity associated with this component + * @csdev: component vitals needed by the framework + */ +struct replicator_drvdata { + struct device *dev; + struct coresight_device *csdev; +}; + +static int replicator_enable(struct coresight_device *csdev, int inport, + int outport) +{ + struct replicator_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); + + dev_info(drvdata->dev, "REPLICATOR enabled\n"); + return 0; +} + +static void replicator_disable(struct coresight_device *csdev, int inport, + int outport) +{ + struct replicator_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); + + dev_info(drvdata->dev, "REPLICATOR disabled\n"); +} + +static const struct coresight_ops_link replicator_link_ops = { + .enable = replicator_enable, + .disable = replicator_disable, +}; + +static const struct coresight_ops replicator_cs_ops = { + .link_ops = &replicator_link_ops, +}; + +static int replicator_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct coresight_platform_data *pdata = NULL; + struct replicator_drvdata *drvdata; + struct coresight_desc *desc; + struct device_node *np = pdev->dev.of_node; + + if (np) { + pdata = of_get_coresight_platform_data(dev, np); + if (IS_ERR(pdata)) + return PTR_ERR(pdata); + pdev->dev.platform_data = pdata; + } + + drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + drvdata->dev = &pdev->dev; + platform_set_drvdata(pdev, drvdata); + + desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL); + if (!desc) + return -ENOMEM; + + desc->type = CORESIGHT_DEV_TYPE_LINK; + desc->subtype.link_subtype = CORESIGHT_DEV_SUBTYPE_LINK_SPLIT; + desc->ops = &replicator_cs_ops; + desc->pdata = pdev->dev.platform_data; + desc->dev = &pdev->dev; + drvdata->csdev = coresight_register(desc); + if (IS_ERR(drvdata->csdev)) + return PTR_ERR(drvdata->csdev); + + dev_info(dev, "REPLICATOR initialized\n"); + return 0; +} + +static int replicator_remove(struct platform_device *pdev) +{ + struct replicator_drvdata *drvdata = platform_get_drvdata(pdev); + + coresight_unregister(drvdata->csdev); + return 0; +} + +static const struct of_device_id replicator_match[] = { + {.compatible = "arm,coresight-replicator"}, + {} +}; + +static struct platform_driver replicator_driver = { + .probe = replicator_probe, + .remove = replicator_remove, + .driver = { + .name = "coresight-replicator", + .of_match_table = replicator_match, + }, +}; + +static int __init replicator_init(void) +{ + return platform_driver_register(&replicator_driver); +} +module_init(replicator_init); + +static void __exit replicator_exit(void) +{ + platform_driver_unregister(&replicator_driver); +} +module_exit(replicator_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("CoreSight Replicator driver"); diff --git a/drivers/hwtracing/coresight/coresight-tmc.c b/drivers/hwtracing/coresight/coresight-tmc.c new file mode 100644 index 0000000..7147f3d --- /dev/null +++ b/drivers/hwtracing/coresight/coresight-tmc.c @@ -0,0 +1,822 @@ +/* Copyright (c) 2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "coresight-priv.h" + +#define TMC_RSZ 0x004 +#define TMC_STS 0x00c +#define TMC_RRD 0x010 +#define TMC_RRP 0x014 +#define TMC_RWP 0x018 +#define TMC_TRG 0x01c +#define TMC_CTL 0x020 +#define TMC_RWD 0x024 +#define TMC_MODE 0x028 +#define TMC_LBUFLEVEL 0x02c +#define TMC_CBUFLEVEL 0x030 +#define TMC_BUFWM 0x034 +#define TMC_RRPHI 0x038 +#define TMC_RWPHI 0x03c +#define TMC_AXICTL 0x110 +#define TMC_DBALO 0x118 +#define TMC_DBAHI 0x11c +#define TMC_FFSR 0x300 +#define TMC_FFCR 0x304 +#define TMC_PSCR 0x308 +#define TMC_ITMISCOP0 0xee0 +#define TMC_ITTRFLIN 0xee8 +#define TMC_ITATBDATA0 0xeec +#define TMC_ITATBCTR2 0xef0 +#define TMC_ITATBCTR1 0xef4 +#define TMC_ITATBCTR0 0xef8 + +/* register description */ +/* TMC_CTL - 0x020 */ +#define TMC_CTL_CAPT_EN BIT(0) +/* TMC_STS - 0x00C */ +#define TMC_STS_TRIGGERED BIT(1) +/* TMC_AXICTL - 0x110 */ +#define TMC_AXICTL_PROT_CTL_B0 BIT(0) +#define TMC_AXICTL_PROT_CTL_B1 BIT(1) +#define TMC_AXICTL_SCT_GAT_MODE BIT(7) +#define TMC_AXICTL_WR_BURST_LEN 0xF00 +/* TMC_FFCR - 0x304 */ +#define TMC_FFCR_EN_FMT BIT(0) +#define TMC_FFCR_EN_TI BIT(1) +#define TMC_FFCR_FON_FLIN BIT(4) +#define TMC_FFCR_FON_TRIG_EVT BIT(5) +#define TMC_FFCR_FLUSHMAN BIT(6) +#define TMC_FFCR_TRIGON_TRIGIN BIT(8) +#define TMC_FFCR_STOP_ON_FLUSH BIT(12) + +#define TMC_STS_TRIGGERED_BIT 2 +#define TMC_FFCR_FLUSHMAN_BIT 6 + +enum tmc_config_type { + TMC_CONFIG_TYPE_ETB, + TMC_CONFIG_TYPE_ETR, + TMC_CONFIG_TYPE_ETF, +}; + +enum tmc_mode { + TMC_MODE_CIRCULAR_BUFFER, + TMC_MODE_SOFTWARE_FIFO, + TMC_MODE_HARDWARE_FIFO, +}; + +enum tmc_mem_intf_width { + TMC_MEM_INTF_WIDTH_32BITS = 0x2, + TMC_MEM_INTF_WIDTH_64BITS = 0x3, + TMC_MEM_INTF_WIDTH_128BITS = 0x4, + TMC_MEM_INTF_WIDTH_256BITS = 0x5, +}; + +/** + * struct tmc_drvdata - specifics associated to an TMC component + * @base: memory mapped base address for this component. + * @dev: the device entity associated to this component. + * @csdev: component vitals needed by the framework. + * @miscdev: specifics to handle "/dev/xyz.tmc" entry. + * @clk: the clock this component is associated to. + * @spinlock: only one at a time pls. + * @read_count: manages preparation of buffer for reading. + * @buf: area of memory where trace data get sent. + * @paddr: DMA start location in RAM. + * @vaddr: virtual representation of @paddr. + * @size: @buf size. + * @enable: this TMC is being used. + * @config_type: TMC variant, must be of type @tmc_config_type. + * @trigger_cntr: amount of words to store after a trigger. + */ +struct tmc_drvdata { + void __iomem *base; + struct device *dev; + struct coresight_device *csdev; + struct miscdevice miscdev; + struct clk *clk; + spinlock_t spinlock; + int read_count; + bool reading; + char *buf; + dma_addr_t paddr; + void __iomem *vaddr; + u32 size; + bool enable; + enum tmc_config_type config_type; + u32 trigger_cntr; +}; + +static void tmc_wait_for_ready(struct tmc_drvdata *drvdata) +{ + /* Ensure formatter, unformatter and hardware fifo are empty */ + if (coresight_timeout(drvdata->base, + TMC_STS, TMC_STS_TRIGGERED_BIT, 1)) { + dev_err(drvdata->dev, + "timeout observed when probing at offset %#x\n", + TMC_STS); + } +} + +static void tmc_flush_and_stop(struct tmc_drvdata *drvdata) +{ + u32 ffcr; + + ffcr = readl_relaxed(drvdata->base + TMC_FFCR); + ffcr |= TMC_FFCR_STOP_ON_FLUSH; + writel_relaxed(ffcr, drvdata->base + TMC_FFCR); + ffcr |= TMC_FFCR_FLUSHMAN; + writel_relaxed(ffcr, drvdata->base + TMC_FFCR); + /* Ensure flush completes */ + if (coresight_timeout(drvdata->base, + TMC_FFCR, TMC_FFCR_FLUSHMAN_BIT, 0)) { + dev_err(drvdata->dev, + "timeout observed when probing at offset %#x\n", + TMC_FFCR); + } + + tmc_wait_for_ready(drvdata); +} + +static void tmc_enable_hw(struct tmc_drvdata *drvdata) +{ + writel_relaxed(TMC_CTL_CAPT_EN, drvdata->base + TMC_CTL); +} + +static void tmc_disable_hw(struct tmc_drvdata *drvdata) +{ + writel_relaxed(0x0, drvdata->base + TMC_CTL); +} + +static void tmc_etb_enable_hw(struct tmc_drvdata *drvdata) +{ + /* Zero out the memory to help with debug */ + memset(drvdata->buf, 0, drvdata->size); + + CS_UNLOCK(drvdata->base); + + writel_relaxed(TMC_MODE_CIRCULAR_BUFFER, drvdata->base + TMC_MODE); + writel_relaxed(TMC_FFCR_EN_FMT | TMC_FFCR_EN_TI | + TMC_FFCR_FON_FLIN | TMC_FFCR_FON_TRIG_EVT | + TMC_FFCR_TRIGON_TRIGIN, + drvdata->base + TMC_FFCR); + + writel_relaxed(drvdata->trigger_cntr, drvdata->base + TMC_TRG); + tmc_enable_hw(drvdata); + + CS_LOCK(drvdata->base); +} + +static void tmc_etr_enable_hw(struct tmc_drvdata *drvdata) +{ + u32 axictl; + + /* Zero out the memory to help with debug */ + memset(drvdata->vaddr, 0, drvdata->size); + + CS_UNLOCK(drvdata->base); + + writel_relaxed(drvdata->size / 4, drvdata->base + TMC_RSZ); + writel_relaxed(TMC_MODE_CIRCULAR_BUFFER, drvdata->base + TMC_MODE); + + axictl = readl_relaxed(drvdata->base + TMC_AXICTL); + axictl |= TMC_AXICTL_WR_BURST_LEN; + writel_relaxed(axictl, drvdata->base + TMC_AXICTL); + axictl &= ~TMC_AXICTL_SCT_GAT_MODE; + writel_relaxed(axictl, drvdata->base + TMC_AXICTL); + axictl = (axictl & + ~(TMC_AXICTL_PROT_CTL_B0 | TMC_AXICTL_PROT_CTL_B1)) | + TMC_AXICTL_PROT_CTL_B1; + writel_relaxed(axictl, drvdata->base + TMC_AXICTL); + + writel_relaxed(drvdata->paddr, drvdata->base + TMC_DBALO); + writel_relaxed(0x0, drvdata->base + TMC_DBAHI); + writel_relaxed(TMC_FFCR_EN_FMT | TMC_FFCR_EN_TI | + TMC_FFCR_FON_FLIN | TMC_FFCR_FON_TRIG_EVT | + TMC_FFCR_TRIGON_TRIGIN, + drvdata->base + TMC_FFCR); + writel_relaxed(drvdata->trigger_cntr, drvdata->base + TMC_TRG); + tmc_enable_hw(drvdata); + + CS_LOCK(drvdata->base); +} + +static void tmc_etf_enable_hw(struct tmc_drvdata *drvdata) +{ + CS_UNLOCK(drvdata->base); + + writel_relaxed(TMC_MODE_HARDWARE_FIFO, drvdata->base + TMC_MODE); + writel_relaxed(TMC_FFCR_EN_FMT | TMC_FFCR_EN_TI, + drvdata->base + TMC_FFCR); + writel_relaxed(0x0, drvdata->base + TMC_BUFWM); + tmc_enable_hw(drvdata); + + CS_LOCK(drvdata->base); +} + +static int tmc_enable(struct tmc_drvdata *drvdata, enum tmc_mode mode) +{ + int ret; + unsigned long flags; + + ret = clk_prepare_enable(drvdata->clk); + if (ret) + return ret; + + spin_lock_irqsave(&drvdata->spinlock, flags); + if (drvdata->reading) { + spin_unlock_irqrestore(&drvdata->spinlock, flags); + clk_disable_unprepare(drvdata->clk); + return -EBUSY; + } + + if (drvdata->config_type == TMC_CONFIG_TYPE_ETB) { + tmc_etb_enable_hw(drvdata); + } else if (drvdata->config_type == TMC_CONFIG_TYPE_ETR) { + tmc_etr_enable_hw(drvdata); + } else { + if (mode == TMC_MODE_CIRCULAR_BUFFER) + tmc_etb_enable_hw(drvdata); + else + tmc_etf_enable_hw(drvdata); + } + drvdata->enable = true; + spin_unlock_irqrestore(&drvdata->spinlock, flags); + + dev_info(drvdata->dev, "TMC enabled\n"); + return 0; +} + +static int tmc_enable_sink(struct coresight_device *csdev) +{ + struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); + + return tmc_enable(drvdata, TMC_MODE_CIRCULAR_BUFFER); +} + +static int tmc_enable_link(struct coresight_device *csdev, int inport, + int outport) +{ + struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); + + return tmc_enable(drvdata, TMC_MODE_HARDWARE_FIFO); +} + +static void tmc_etb_dump_hw(struct tmc_drvdata *drvdata) +{ + enum tmc_mem_intf_width memwidth; + u8 memwords; + char *bufp; + u32 read_data; + int i; + + memwidth = BMVAL(readl_relaxed(drvdata->base + CORESIGHT_DEVID), 8, 10); + if (memwidth == TMC_MEM_INTF_WIDTH_32BITS) + memwords = 1; + else if (memwidth == TMC_MEM_INTF_WIDTH_64BITS) + memwords = 2; + else if (memwidth == TMC_MEM_INTF_WIDTH_128BITS) + memwords = 4; + else + memwords = 8; + + bufp = drvdata->buf; + while (1) { + for (i = 0; i < memwords; i++) { + read_data = readl_relaxed(drvdata->base + TMC_RRD); + if (read_data == 0xFFFFFFFF) + return; + memcpy(bufp, &read_data, 4); + bufp += 4; + } + } +} + +static void tmc_etb_disable_hw(struct tmc_drvdata *drvdata) +{ + CS_UNLOCK(drvdata->base); + + tmc_flush_and_stop(drvdata); + tmc_etb_dump_hw(drvdata); + tmc_disable_hw(drvdata); + + CS_LOCK(drvdata->base); +} + +static void tmc_etr_dump_hw(struct tmc_drvdata *drvdata) +{ + u32 rwp, val; + + rwp = readl_relaxed(drvdata->base + TMC_RWP); + val = readl_relaxed(drvdata->base + TMC_STS); + + /* How much memory do we still have */ + if (val & BIT(0)) + drvdata->buf = drvdata->vaddr + rwp - drvdata->paddr; + else + drvdata->buf = drvdata->vaddr; +} + +static void tmc_etr_disable_hw(struct tmc_drvdata *drvdata) +{ + CS_UNLOCK(drvdata->base); + + tmc_flush_and_stop(drvdata); + tmc_etr_dump_hw(drvdata); + tmc_disable_hw(drvdata); + + CS_LOCK(drvdata->base); +} + +static void tmc_etf_disable_hw(struct tmc_drvdata *drvdata) +{ + CS_UNLOCK(drvdata->base); + + tmc_flush_and_stop(drvdata); + tmc_disable_hw(drvdata); + + CS_LOCK(drvdata->base); +} + +static void tmc_disable(struct tmc_drvdata *drvdata, enum tmc_mode mode) +{ + unsigned long flags; + + spin_lock_irqsave(&drvdata->spinlock, flags); + if (drvdata->reading) + goto out; + + if (drvdata->config_type == TMC_CONFIG_TYPE_ETB) { + tmc_etb_disable_hw(drvdata); + } else if (drvdata->config_type == TMC_CONFIG_TYPE_ETR) { + tmc_etr_disable_hw(drvdata); + } else { + if (mode == TMC_MODE_CIRCULAR_BUFFER) + tmc_etb_disable_hw(drvdata); + else + tmc_etf_disable_hw(drvdata); + } +out: + drvdata->enable = false; + spin_unlock_irqrestore(&drvdata->spinlock, flags); + + clk_disable_unprepare(drvdata->clk); + + dev_info(drvdata->dev, "TMC disabled\n"); +} + +static void tmc_disable_sink(struct coresight_device *csdev) +{ + struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); + + tmc_disable(drvdata, TMC_MODE_CIRCULAR_BUFFER); +} + +static void tmc_disable_link(struct coresight_device *csdev, int inport, + int outport) +{ + struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); + + tmc_disable(drvdata, TMC_MODE_HARDWARE_FIFO); +} + +static const struct coresight_ops_sink tmc_sink_ops = { + .enable = tmc_enable_sink, + .disable = tmc_disable_sink, +}; + +static const struct coresight_ops_link tmc_link_ops = { + .enable = tmc_enable_link, + .disable = tmc_disable_link, +}; + +static const struct coresight_ops tmc_etb_cs_ops = { + .sink_ops = &tmc_sink_ops, +}; + +static const struct coresight_ops tmc_etr_cs_ops = { + .sink_ops = &tmc_sink_ops, +}; + +static const struct coresight_ops tmc_etf_cs_ops = { + .sink_ops = &tmc_sink_ops, + .link_ops = &tmc_link_ops, +}; + +static int tmc_read_prepare(struct tmc_drvdata *drvdata) +{ + int ret; + unsigned long flags; + enum tmc_mode mode; + + spin_lock_irqsave(&drvdata->spinlock, flags); + if (!drvdata->enable) + goto out; + + if (drvdata->config_type == TMC_CONFIG_TYPE_ETB) { + tmc_etb_disable_hw(drvdata); + } else if (drvdata->config_type == TMC_CONFIG_TYPE_ETR) { + tmc_etr_disable_hw(drvdata); + } else { + mode = readl_relaxed(drvdata->base + TMC_MODE); + if (mode == TMC_MODE_CIRCULAR_BUFFER) { + tmc_etb_disable_hw(drvdata); + } else { + ret = -ENODEV; + goto err; + } + } +out: + drvdata->reading = true; + spin_unlock_irqrestore(&drvdata->spinlock, flags); + + dev_info(drvdata->dev, "TMC read start\n"); + return 0; +err: + spin_unlock_irqrestore(&drvdata->spinlock, flags); + return ret; +} + +static void tmc_read_unprepare(struct tmc_drvdata *drvdata) +{ + unsigned long flags; + enum tmc_mode mode; + + spin_lock_irqsave(&drvdata->spinlock, flags); + if (!drvdata->enable) + goto out; + + if (drvdata->config_type == TMC_CONFIG_TYPE_ETB) { + tmc_etb_enable_hw(drvdata); + } else if (drvdata->config_type == TMC_CONFIG_TYPE_ETR) { + tmc_etr_enable_hw(drvdata); + } else { + mode = readl_relaxed(drvdata->base + TMC_MODE); + if (mode == TMC_MODE_CIRCULAR_BUFFER) + tmc_etb_enable_hw(drvdata); + } +out: + drvdata->reading = false; + spin_unlock_irqrestore(&drvdata->spinlock, flags); + + dev_info(drvdata->dev, "TMC read end\n"); +} + +static int tmc_open(struct inode *inode, struct file *file) +{ + struct tmc_drvdata *drvdata = container_of(file->private_data, + struct tmc_drvdata, miscdev); + int ret = 0; + + if (drvdata->read_count++) + goto out; + + ret = tmc_read_prepare(drvdata); + if (ret) + return ret; +out: + nonseekable_open(inode, file); + + dev_dbg(drvdata->dev, "%s: successfully opened\n", __func__); + return 0; +} + +static ssize_t tmc_read(struct file *file, char __user *data, size_t len, + loff_t *ppos) +{ + struct tmc_drvdata *drvdata = container_of(file->private_data, + struct tmc_drvdata, miscdev); + char *bufp = drvdata->buf + *ppos; + + if (*ppos + len > drvdata->size) + len = drvdata->size - *ppos; + + if (drvdata->config_type == TMC_CONFIG_TYPE_ETR) { + if (bufp == (char *)(drvdata->vaddr + drvdata->size)) + bufp = drvdata->vaddr; + else if (bufp > (char *)(drvdata->vaddr + drvdata->size)) + bufp -= drvdata->size; + if ((bufp + len) > (char *)(drvdata->vaddr + drvdata->size)) + len = (char *)(drvdata->vaddr + drvdata->size) - bufp; + } + + if (copy_to_user(data, bufp, len)) { + dev_dbg(drvdata->dev, "%s: copy_to_user failed\n", __func__); + return -EFAULT; + } + + *ppos += len; + + dev_dbg(drvdata->dev, "%s: %zu bytes copied, %d bytes left\n", + __func__, len, (int)(drvdata->size - *ppos)); + return len; +} + +static int tmc_release(struct inode *inode, struct file *file) +{ + struct tmc_drvdata *drvdata = container_of(file->private_data, + struct tmc_drvdata, miscdev); + + if (--drvdata->read_count) { + if (drvdata->read_count < 0) { + dev_err(drvdata->dev, "mismatched close\n"); + drvdata->read_count = 0; + } + goto out; + } + + tmc_read_unprepare(drvdata); +out: + dev_dbg(drvdata->dev, "%s: released\n", __func__); + return 0; +} + +static const struct file_operations tmc_fops = { + .owner = THIS_MODULE, + .open = tmc_open, + .read = tmc_read, + .release = tmc_release, + .llseek = no_llseek, +}; + +static ssize_t status_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret; + unsigned long flags; + u32 tmc_rsz, tmc_sts, tmc_rrp, tmc_rwp, tmc_trg; + u32 tmc_ctl, tmc_ffsr, tmc_ffcr, tmc_mode, tmc_pscr; + u32 devid; + struct tmc_drvdata *drvdata = dev_get_drvdata(dev->parent); + + ret = clk_prepare_enable(drvdata->clk); + if (ret) + goto out; + + spin_lock_irqsave(&drvdata->spinlock, flags); + CS_UNLOCK(drvdata->base); + + tmc_rsz = readl_relaxed(drvdata->base + TMC_RSZ); + tmc_sts = readl_relaxed(drvdata->base + TMC_STS); + tmc_rrp = readl_relaxed(drvdata->base + TMC_RRP); + tmc_rwp = readl_relaxed(drvdata->base + TMC_RWP); + tmc_trg = readl_relaxed(drvdata->base + TMC_TRG); + tmc_ctl = readl_relaxed(drvdata->base + TMC_CTL); + tmc_ffsr = readl_relaxed(drvdata->base + TMC_FFSR); + tmc_ffcr = readl_relaxed(drvdata->base + TMC_FFCR); + tmc_mode = readl_relaxed(drvdata->base + TMC_MODE); + tmc_pscr = readl_relaxed(drvdata->base + TMC_PSCR); + devid = readl_relaxed(drvdata->base + CORESIGHT_DEVID); + + CS_LOCK(drvdata->base); + spin_unlock_irqrestore(&drvdata->spinlock, flags); + + clk_disable_unprepare(drvdata->clk); + + return sprintf(buf, + "Depth:\t\t0x%x\n" + "Status:\t\t0x%x\n" + "RAM read ptr:\t0x%x\n" + "RAM wrt ptr:\t0x%x\n" + "Trigger cnt:\t0x%x\n" + "Control:\t0x%x\n" + "Flush status:\t0x%x\n" + "Flush ctrl:\t0x%x\n" + "Mode:\t\t0x%x\n" + "PSRC:\t\t0x%x\n" + "DEVID:\t\t0x%x\n", + tmc_rsz, tmc_sts, tmc_rrp, tmc_rwp, tmc_trg, + tmc_ctl, tmc_ffsr, tmc_ffcr, tmc_mode, tmc_pscr, devid); +out: + return -EINVAL; +} +static DEVICE_ATTR_RO(status); + +static ssize_t trigger_cntr_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct tmc_drvdata *drvdata = dev_get_drvdata(dev->parent); + unsigned long val = drvdata->trigger_cntr; + + return sprintf(buf, "%#lx\n", val); +} + +static ssize_t trigger_cntr_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int ret; + unsigned long val; + struct tmc_drvdata *drvdata = dev_get_drvdata(dev->parent); + + ret = kstrtoul(buf, 16, &val); + if (ret) + return ret; + + drvdata->trigger_cntr = val; + return size; +} +static DEVICE_ATTR_RW(trigger_cntr); + +static struct attribute *coresight_etb_attrs[] = { + &dev_attr_trigger_cntr.attr, + &dev_attr_status.attr, + NULL, +}; +ATTRIBUTE_GROUPS(coresight_etb); + +static struct attribute *coresight_etr_attrs[] = { + &dev_attr_trigger_cntr.attr, + &dev_attr_status.attr, + NULL, +}; +ATTRIBUTE_GROUPS(coresight_etr); + +static struct attribute *coresight_etf_attrs[] = { + &dev_attr_trigger_cntr.attr, + &dev_attr_status.attr, + NULL, +}; +ATTRIBUTE_GROUPS(coresight_etf); + +static int tmc_probe(struct amba_device *adev, const struct amba_id *id) +{ + int ret = 0; + u32 devid; + void __iomem *base; + struct device *dev = &adev->dev; + struct coresight_platform_data *pdata = NULL; + struct tmc_drvdata *drvdata; + struct resource *res = &adev->res; + struct coresight_desc *desc; + struct device_node *np = adev->dev.of_node; + + if (np) { + pdata = of_get_coresight_platform_data(dev, np); + if (IS_ERR(pdata)) + return PTR_ERR(pdata); + adev->dev.platform_data = pdata; + } + + drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + drvdata->dev = &adev->dev; + dev_set_drvdata(dev, drvdata); + + /* Validity for the resource is already checked by the AMBA core */ + base = devm_ioremap_resource(dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + drvdata->base = base; + + spin_lock_init(&drvdata->spinlock); + + drvdata->clk = adev->pclk; + ret = clk_prepare_enable(drvdata->clk); + if (ret) + return ret; + + devid = readl_relaxed(drvdata->base + CORESIGHT_DEVID); + drvdata->config_type = BMVAL(devid, 6, 7); + + if (drvdata->config_type == TMC_CONFIG_TYPE_ETR) { + if (np) + ret = of_property_read_u32(np, + "arm,buffer-size", + &drvdata->size); + if (ret) + drvdata->size = SZ_1M; + } else { + drvdata->size = readl_relaxed(drvdata->base + TMC_RSZ) * 4; + } + + clk_disable_unprepare(drvdata->clk); + + if (drvdata->config_type == TMC_CONFIG_TYPE_ETR) { + drvdata->vaddr = dma_alloc_coherent(dev, drvdata->size, + &drvdata->paddr, GFP_KERNEL); + if (!drvdata->vaddr) + return -ENOMEM; + + memset(drvdata->vaddr, 0, drvdata->size); + drvdata->buf = drvdata->vaddr; + } else { + drvdata->buf = devm_kzalloc(dev, drvdata->size, GFP_KERNEL); + if (!drvdata->buf) + return -ENOMEM; + } + + desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL); + if (!desc) { + ret = -ENOMEM; + goto err_devm_kzalloc; + } + + desc->pdata = pdata; + desc->dev = dev; + desc->subtype.sink_subtype = CORESIGHT_DEV_SUBTYPE_SINK_BUFFER; + + if (drvdata->config_type == TMC_CONFIG_TYPE_ETB) { + desc->type = CORESIGHT_DEV_TYPE_SINK; + desc->ops = &tmc_etb_cs_ops; + desc->groups = coresight_etb_groups; + } else if (drvdata->config_type == TMC_CONFIG_TYPE_ETR) { + desc->type = CORESIGHT_DEV_TYPE_SINK; + desc->ops = &tmc_etr_cs_ops; + desc->groups = coresight_etr_groups; + } else { + desc->type = CORESIGHT_DEV_TYPE_LINKSINK; + desc->subtype.link_subtype = CORESIGHT_DEV_SUBTYPE_LINK_FIFO; + desc->ops = &tmc_etf_cs_ops; + desc->groups = coresight_etf_groups; + } + + drvdata->csdev = coresight_register(desc); + if (IS_ERR(drvdata->csdev)) { + ret = PTR_ERR(drvdata->csdev); + goto err_devm_kzalloc; + } + + drvdata->miscdev.name = pdata->name; + drvdata->miscdev.minor = MISC_DYNAMIC_MINOR; + drvdata->miscdev.fops = &tmc_fops; + ret = misc_register(&drvdata->miscdev); + if (ret) + goto err_misc_register; + + dev_info(dev, "TMC initialized\n"); + return 0; + +err_misc_register: + coresight_unregister(drvdata->csdev); +err_devm_kzalloc: + if (drvdata->config_type == TMC_CONFIG_TYPE_ETR) + dma_free_coherent(dev, drvdata->size, + &drvdata->paddr, GFP_KERNEL); + 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, + .mask = 0x0003ffff, + }, + { 0, 0}, +}; + +static struct amba_driver tmc_driver = { + .drv = { + .name = "coresight-tmc", + .owner = THIS_MODULE, + }, + .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"); diff --git a/drivers/hwtracing/coresight/coresight-tpiu.c b/drivers/hwtracing/coresight/coresight-tpiu.c new file mode 100644 index 0000000..3b33af2 --- /dev/null +++ b/drivers/hwtracing/coresight/coresight-tpiu.c @@ -0,0 +1,207 @@ +/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "coresight-priv.h" + +#define TPIU_SUPP_PORTSZ 0x000 +#define TPIU_CURR_PORTSZ 0x004 +#define TPIU_SUPP_TRIGMODES 0x100 +#define TPIU_TRIG_CNTRVAL 0x104 +#define TPIU_TRIG_MULT 0x108 +#define TPIU_SUPP_TESTPATM 0x200 +#define TPIU_CURR_TESTPATM 0x204 +#define TPIU_TEST_PATREPCNTR 0x208 +#define TPIU_FFSR 0x300 +#define TPIU_FFCR 0x304 +#define TPIU_FSYNC_CNTR 0x308 +#define TPIU_EXTCTL_INPORT 0x400 +#define TPIU_EXTCTL_OUTPORT 0x404 +#define TPIU_ITTRFLINACK 0xee4 +#define TPIU_ITTRFLIN 0xee8 +#define TPIU_ITATBDATA0 0xeec +#define TPIU_ITATBCTR2 0xef0 +#define TPIU_ITATBCTR1 0xef4 +#define TPIU_ITATBCTR0 0xef8 + +/** register definition **/ +/* FFCR - 0x304 */ +#define FFCR_FON_MAN BIT(6) + +/** + * @base: memory mapped base address for this component. + * @dev: the device entity associated to this component. + * @csdev: component vitals needed by the framework. + * @clk: the clock this component is associated to. + */ +struct tpiu_drvdata { + void __iomem *base; + struct device *dev; + struct coresight_device *csdev; + struct clk *clk; +}; + +static void tpiu_enable_hw(struct tpiu_drvdata *drvdata) +{ + CS_UNLOCK(drvdata->base); + + /* TODO: fill this up */ + + CS_LOCK(drvdata->base); +} + +static int tpiu_enable(struct coresight_device *csdev) +{ + struct tpiu_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); + int ret; + + ret = clk_prepare_enable(drvdata->clk); + if (ret) + return ret; + + tpiu_enable_hw(drvdata); + + dev_info(drvdata->dev, "TPIU enabled\n"); + return 0; +} + +static void tpiu_disable_hw(struct tpiu_drvdata *drvdata) +{ + CS_UNLOCK(drvdata->base); + + /* Clear formatter controle reg. */ + writel_relaxed(0x0, drvdata->base + TPIU_FFCR); + /* Generate manual flush */ + writel_relaxed(FFCR_FON_MAN, drvdata->base + TPIU_FFCR); + + CS_LOCK(drvdata->base); +} + +static void tpiu_disable(struct coresight_device *csdev) +{ + struct tpiu_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); + + tpiu_disable_hw(drvdata); + + clk_disable_unprepare(drvdata->clk); + + dev_info(drvdata->dev, "TPIU disabled\n"); +} + +static const struct coresight_ops_sink tpiu_sink_ops = { + .enable = tpiu_enable, + .disable = tpiu_disable, +}; + +static const struct coresight_ops tpiu_cs_ops = { + .sink_ops = &tpiu_sink_ops, +}; + +static int tpiu_probe(struct amba_device *adev, const struct amba_id *id) +{ + int ret; + void __iomem *base; + struct device *dev = &adev->dev; + struct coresight_platform_data *pdata = NULL; + struct tpiu_drvdata *drvdata; + struct resource *res = &adev->res; + struct coresight_desc *desc; + struct device_node *np = adev->dev.of_node; + + if (np) { + pdata = of_get_coresight_platform_data(dev, np); + if (IS_ERR(pdata)) + return PTR_ERR(pdata); + adev->dev.platform_data = pdata; + } + + drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + drvdata->dev = &adev->dev; + dev_set_drvdata(dev, drvdata); + + /* Validity for the resource is already checked by the AMBA core */ + base = devm_ioremap_resource(dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + drvdata->base = base; + + drvdata->clk = adev->pclk; + ret = clk_prepare_enable(drvdata->clk); + if (ret) + return ret; + + /* Disable tpiu to support older devices */ + tpiu_disable_hw(drvdata); + + clk_disable_unprepare(drvdata->clk); + + desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL); + if (!desc) + return -ENOMEM; + + desc->type = CORESIGHT_DEV_TYPE_SINK; + desc->subtype.sink_subtype = CORESIGHT_DEV_SUBTYPE_SINK_PORT; + desc->ops = &tpiu_cs_ops; + desc->pdata = pdata; + desc->dev = dev; + drvdata->csdev = coresight_register(desc); + if (IS_ERR(drvdata->csdev)) + return PTR_ERR(drvdata->csdev); + + dev_info(dev, "TPIU initialized\n"); + return 0; +} + +static int tpiu_remove(struct amba_device *adev) +{ + struct tpiu_drvdata *drvdata = amba_get_drvdata(adev); + + coresight_unregister(drvdata->csdev); + return 0; +} + +static struct amba_id tpiu_ids[] = { + { + .id = 0x0003b912, + .mask = 0x0003ffff, + }, + { 0, 0}, +}; + +static struct amba_driver tpiu_driver = { + .drv = { + .name = "coresight-tpiu", + .owner = THIS_MODULE, + }, + .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"); diff --git a/drivers/hwtracing/coresight/coresight.c b/drivers/hwtracing/coresight/coresight.c new file mode 100644 index 0000000..894531d --- /dev/null +++ b/drivers/hwtracing/coresight/coresight.c @@ -0,0 +1,720 @@ +/* Copyright (c) 2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "coresight-priv.h" + +static DEFINE_MUTEX(coresight_mutex); + +static int coresight_id_match(struct device *dev, void *data) +{ + int trace_id, i_trace_id; + struct coresight_device *csdev, *i_csdev; + + csdev = data; + i_csdev = to_coresight_device(dev); + + /* + * No need to care about oneself and components that are not + * sources or not enabled + */ + if (i_csdev == csdev || !i_csdev->enable || + i_csdev->type != CORESIGHT_DEV_TYPE_SOURCE) + return 0; + + /* Get the source ID for both compoment */ + trace_id = source_ops(csdev)->trace_id(csdev); + i_trace_id = source_ops(i_csdev)->trace_id(i_csdev); + + /* All you need is one */ + if (trace_id == i_trace_id) + return 1; + + return 0; +} + +static int coresight_source_is_unique(struct coresight_device *csdev) +{ + int trace_id = source_ops(csdev)->trace_id(csdev); + + /* this shouldn't happen */ + if (trace_id < 0) + return 0; + + return !bus_for_each_dev(&coresight_bustype, NULL, + csdev, coresight_id_match); +} + +static int coresight_find_link_inport(struct coresight_device *csdev) +{ + 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) + return conn->child_port; + } + + dev_err(&csdev->dev, "couldn't find inport, parent: %s, child: %s\n", + dev_name(&parent->dev), dev_name(&csdev->dev)); + + return 0; +} + +static int coresight_find_link_outport(struct coresight_device *csdev) +{ + 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) + return conn->outport; + } + + dev_err(&csdev->dev, "couldn't find outport, parent: %s, child: %s\n", + dev_name(&csdev->dev), dev_name(&child->dev)); + + return 0; +} + +static int coresight_enable_sink(struct coresight_device *csdev) +{ + int ret; + + if (!csdev->enable) { + if (sink_ops(csdev)->enable) { + ret = sink_ops(csdev)->enable(csdev); + if (ret) + return ret; + } + csdev->enable = true; + } + + atomic_inc(csdev->refcnt); + + return 0; +} + +static void coresight_disable_sink(struct coresight_device *csdev) +{ + if (atomic_dec_return(csdev->refcnt) == 0) { + if (sink_ops(csdev)->disable) { + sink_ops(csdev)->disable(csdev); + csdev->enable = false; + } + } +} + +static int coresight_enable_link(struct coresight_device *csdev) +{ + int ret; + int link_subtype; + int refport, inport, outport; + + inport = coresight_find_link_inport(csdev); + outport = coresight_find_link_outport(csdev); + link_subtype = csdev->subtype.link_subtype; + + if (link_subtype == CORESIGHT_DEV_SUBTYPE_LINK_MERG) + refport = inport; + else if (link_subtype == CORESIGHT_DEV_SUBTYPE_LINK_SPLIT) + refport = outport; + else + refport = 0; + + if (atomic_inc_return(&csdev->refcnt[refport]) == 1) { + if (link_ops(csdev)->enable) { + ret = link_ops(csdev)->enable(csdev, inport, outport); + if (ret) + return ret; + } + } + + csdev->enable = true; + + return 0; +} + +static void coresight_disable_link(struct coresight_device *csdev) +{ + int i, nr_conns; + int link_subtype; + int refport, inport, outport; + + inport = coresight_find_link_inport(csdev); + outport = coresight_find_link_outport(csdev); + link_subtype = csdev->subtype.link_subtype; + + if (link_subtype == CORESIGHT_DEV_SUBTYPE_LINK_MERG) { + refport = inport; + nr_conns = csdev->nr_inport; + } else if (link_subtype == CORESIGHT_DEV_SUBTYPE_LINK_SPLIT) { + refport = outport; + nr_conns = csdev->nr_outport; + } else { + refport = 0; + nr_conns = 1; + } + + if (atomic_dec_return(&csdev->refcnt[refport]) == 0) { + if (link_ops(csdev)->disable) + link_ops(csdev)->disable(csdev, inport, outport); + } + + for (i = 0; i < nr_conns; i++) + if (atomic_read(&csdev->refcnt[i]) != 0) + return; + + csdev->enable = false; +} + +static int coresight_enable_source(struct coresight_device *csdev) +{ + int ret; + + if (!coresight_source_is_unique(csdev)) { + dev_warn(&csdev->dev, "traceID %d not unique\n", + source_ops(csdev)->trace_id(csdev)); + return -EINVAL; + } + + if (!csdev->enable) { + if (source_ops(csdev)->enable) { + ret = source_ops(csdev)->enable(csdev); + if (ret) + return ret; + } + csdev->enable = true; + } + + atomic_inc(csdev->refcnt); + + return 0; +} + +static void coresight_disable_source(struct coresight_device *csdev) +{ + if (atomic_dec_return(csdev->refcnt) == 0) { + if (source_ops(csdev)->disable) { + source_ops(csdev)->disable(csdev); + csdev->enable = false; + } + } +} + +static int coresight_enable_path(struct list_head *path) +{ + int ret = 0; + struct coresight_device *cd; + + 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) + 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); + } + } + + return ret; +} + +static int coresight_disable_path(struct list_head *path) +{ + struct coresight_device *cd; + + 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); + } + } + + return 0; +} + +static int coresight_build_paths(struct coresight_device *csdev, + struct list_head *path, + bool enable) +{ + int i, ret = -EINVAL; + struct coresight_connection *conn; + + list_add(&csdev->path_link, path); + + 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; + } + } + + if (list_first_entry(path, struct coresight_device, path_link) != csdev) + dev_err(&csdev->dev, "wrong device in %s\n", __func__); + + list_del(&csdev->path_link); + + return ret; +} + +int coresight_enable(struct coresight_device *csdev) +{ + int ret = 0; + LIST_HEAD(path); + + mutex_lock(&coresight_mutex); + if (csdev->type != CORESIGHT_DEV_TYPE_SOURCE) { + ret = -EINVAL; + dev_err(&csdev->dev, "wrong device type in %s\n", __func__); + goto out; + } + if (csdev->enable) + goto out; + + if (coresight_build_paths(csdev, &path, true)) { + dev_err(&csdev->dev, "building path(s) failed\n"); + goto out; + } + + if (coresight_enable_source(csdev)) + dev_err(&csdev->dev, "source enable failed\n"); +out: + mutex_unlock(&coresight_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(coresight_enable); + +void coresight_disable(struct coresight_device *csdev) +{ + LIST_HEAD(path); + + mutex_lock(&coresight_mutex); + if (csdev->type != CORESIGHT_DEV_TYPE_SOURCE) { + dev_err(&csdev->dev, "wrong device type in %s\n", __func__); + goto out; + } + if (!csdev->enable) + goto out; + + coresight_disable_source(csdev); + if (coresight_build_paths(csdev, &path, false)) + dev_err(&csdev->dev, "releasing path(s) failed\n"); + +out: + mutex_unlock(&coresight_mutex); +} +EXPORT_SYMBOL_GPL(coresight_disable); + +static ssize_t enable_sink_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct coresight_device *csdev = to_coresight_device(dev); + + return scnprintf(buf, PAGE_SIZE, "%u\n", (unsigned)csdev->activated); +} + +static ssize_t enable_sink_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int ret; + unsigned long val; + struct coresight_device *csdev = to_coresight_device(dev); + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + + if (val) + csdev->activated = true; + else + csdev->activated = false; + + return size; + +} +static DEVICE_ATTR_RW(enable_sink); + +static ssize_t enable_source_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct coresight_device *csdev = to_coresight_device(dev); + + return scnprintf(buf, PAGE_SIZE, "%u\n", (unsigned)csdev->enable); +} + +static ssize_t enable_source_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int ret = 0; + unsigned long val; + struct coresight_device *csdev = to_coresight_device(dev); + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + + if (val) { + ret = coresight_enable(csdev); + if (ret) + return ret; + } else { + coresight_disable(csdev); + } + + return size; +} +static DEVICE_ATTR_RW(enable_source); + +static struct attribute *coresight_sink_attrs[] = { + &dev_attr_enable_sink.attr, + NULL, +}; +ATTRIBUTE_GROUPS(coresight_sink); + +static struct attribute *coresight_source_attrs[] = { + &dev_attr_enable_source.attr, + NULL, +}; +ATTRIBUTE_GROUPS(coresight_source); + +static struct device_type coresight_dev_type[] = { + { + .name = "none", + }, + { + .name = "sink", + .groups = coresight_sink_groups, + }, + { + .name = "link", + }, + { + .name = "linksink", + .groups = coresight_sink_groups, + }, + { + .name = "source", + .groups = coresight_source_groups, + }, +}; + +static void coresight_device_release(struct device *dev) +{ + struct coresight_device *csdev = to_coresight_device(dev); + + kfree(csdev); +} + +static int coresight_orphan_match(struct device *dev, void *data) +{ + int i; + bool still_orphan = false; + struct coresight_device *csdev, *i_csdev; + struct coresight_connection *conn; + + csdev = data; + i_csdev = to_coresight_device(dev); + + /* No need to check oneself */ + if (csdev == i_csdev) + return 0; + + /* Move on to another component if no connection is orphan */ + if (!i_csdev->orphan) + return 0; + /* + * Circle throuch all the connection of that component. If we find + * an orphan connection whose name matches @csdev, link it. + */ + for (i = 0; i < i_csdev->nr_outport; i++) { + conn = &i_csdev->conns[i]; + + /* We have found at least one orphan connection */ + if (conn->child_dev == NULL) { + /* Does it match this newly added device? */ + if (!strcmp(dev_name(&csdev->dev), conn->child_name)) { + conn->child_dev = csdev; + } else { + /* This component still has an orphan */ + still_orphan = true; + } + } + } + + i_csdev->orphan = still_orphan; + + /* + * Returning '0' ensures that all known component on the + * bus will be checked. + */ + return 0; +} + +static void coresight_fixup_orphan_conns(struct coresight_device *csdev) +{ + /* + * No need to check for a return value as orphan connection(s) + * are hooked-up with each newly added component. + */ + bus_for_each_dev(&coresight_bustype, NULL, + csdev, coresight_orphan_match); +} + + +static int coresight_name_match(struct device *dev, void *data) +{ + char *to_match; + struct coresight_device *i_csdev; + + to_match = data; + i_csdev = to_coresight_device(dev); + + if (!strcmp(to_match, dev_name(&i_csdev->dev))) + return 1; + + return 0; +} + +static void coresight_fixup_device_conns(struct coresight_device *csdev) +{ + int i; + struct device *dev = NULL; + struct coresight_connection *conn; + + for (i = 0; i < csdev->nr_outport; i++) { + conn = &csdev->conns[i]; + dev = bus_find_device(&coresight_bustype, NULL, + (void *)conn->child_name, + coresight_name_match); + + if (dev) { + conn->child_dev = to_coresight_device(dev); + } else { + csdev->orphan = true; + conn->child_dev = NULL; + } + } +} + +/** + * coresight_timeout - loop until a bit has changed to a specific state. + * @addr: base address of the area of interest. + * @offset: address of a register, starting from @addr. + * @position: the position of the bit of interest. + * @value: the value the bit should have. + * + * Return: 0 as soon as the bit has taken the desired state or -EAGAIN if + * TIMEOUT_US has elapsed, which ever happens first. + */ + +int coresight_timeout(void __iomem *addr, u32 offset, int position, int value) +{ + int i; + u32 val; + + for (i = TIMEOUT_US; i > 0; i--) { + val = __raw_readl(addr + offset); + /* waiting on the bit to go from 0 to 1 */ + if (value) { + if (val & BIT(position)) + return 0; + /* waiting on the bit to go from 1 to 0 */ + } else { + if (!(val & BIT(position))) + return 0; + } + + /* + * Delay is arbitrary - the specification doesn't say how long + * we are expected to wait. Extra check required to make sure + * we don't wait needlessly on the last iteration. + */ + if (i - 1) + udelay(1); + } + + return -EAGAIN; +} + +struct bus_type coresight_bustype = { + .name = "coresight", +}; + +static int __init coresight_init(void) +{ + return bus_register(&coresight_bustype); +} +postcore_initcall(coresight_init); + +struct coresight_device *coresight_register(struct coresight_desc *desc) +{ + int i; + int ret; + int link_subtype; + int nr_refcnts = 1; + atomic_t *refcnts = NULL; + struct coresight_device *csdev; + struct coresight_connection *conns; + + csdev = kzalloc(sizeof(*csdev), GFP_KERNEL); + if (!csdev) { + ret = -ENOMEM; + goto err_kzalloc_csdev; + } + + if (desc->type == CORESIGHT_DEV_TYPE_LINK || + desc->type == CORESIGHT_DEV_TYPE_LINKSINK) { + link_subtype = desc->subtype.link_subtype; + + if (link_subtype == CORESIGHT_DEV_SUBTYPE_LINK_MERG) + nr_refcnts = desc->pdata->nr_inport; + else if (link_subtype == CORESIGHT_DEV_SUBTYPE_LINK_SPLIT) + nr_refcnts = desc->pdata->nr_outport; + } + + refcnts = kcalloc(nr_refcnts, sizeof(*refcnts), GFP_KERNEL); + if (!refcnts) { + ret = -ENOMEM; + goto err_kzalloc_refcnts; + } + + csdev->refcnt = refcnts; + + csdev->nr_inport = desc->pdata->nr_inport; + csdev->nr_outport = desc->pdata->nr_outport; + conns = kcalloc(csdev->nr_outport, sizeof(*conns), GFP_KERNEL); + if (!conns) { + ret = -ENOMEM; + goto err_kzalloc_conns; + } + + for (i = 0; i < csdev->nr_outport; i++) { + conns[i].outport = desc->pdata->outports[i]; + conns[i].child_name = desc->pdata->child_names[i]; + conns[i].child_port = desc->pdata->child_ports[i]; + } + + csdev->conns = conns; + + csdev->type = desc->type; + csdev->subtype = desc->subtype; + csdev->ops = desc->ops; + csdev->orphan = false; + + csdev->dev.type = &coresight_dev_type[desc->type]; + csdev->dev.groups = desc->groups; + csdev->dev.parent = desc->dev; + csdev->dev.release = coresight_device_release; + csdev->dev.bus = &coresight_bustype; + dev_set_name(&csdev->dev, "%s", desc->pdata->name); + + ret = device_register(&csdev->dev); + if (ret) + goto err_device_register; + + mutex_lock(&coresight_mutex); + + coresight_fixup_device_conns(csdev); + coresight_fixup_orphan_conns(csdev); + + mutex_unlock(&coresight_mutex); + + return csdev; + +err_device_register: + kfree(conns); +err_kzalloc_conns: + kfree(refcnts); +err_kzalloc_refcnts: + kfree(csdev); +err_kzalloc_csdev: + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(coresight_register); + +void coresight_unregister(struct coresight_device *csdev) +{ + mutex_lock(&coresight_mutex); + + kfree(csdev->conns); + device_unregister(&csdev->dev); + + mutex_unlock(&coresight_mutex); +} +EXPORT_SYMBOL_GPL(coresight_unregister); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/hwtracing/coresight/of_coresight.c b/drivers/hwtracing/coresight/of_coresight.c new file mode 100644 index 0000000..f3cc8e9 --- /dev/null +++ b/drivers/hwtracing/coresight/of_coresight.c @@ -0,0 +1,200 @@ +/* Copyright (c) 2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +static int of_dev_node_match(struct device *dev, void *data) +{ + return dev->of_node == data; +} + +static struct device * +of_coresight_get_endpoint_device(struct device_node *endpoint) +{ + struct device *dev = NULL; + + /* + * If we have a non-configuable replicator, it will be found on the + * platform bus. + */ + dev = bus_find_device(&platform_bus_type, NULL, + endpoint, of_dev_node_match); + if (dev) + return dev; + + /* + * We have a configurable component - circle through the AMBA bus + * looking for the device that matches the endpoint node. + */ + return bus_find_device(&amba_bustype, NULL, + endpoint, of_dev_node_match); +} + +static struct device_node *of_get_coresight_endpoint( + const struct device_node *parent, struct device_node *prev) +{ + struct device_node *node = of_graph_get_next_endpoint(parent, prev); + + of_node_put(prev); + return node; +} + +static void of_coresight_get_ports(struct device_node *node, + int *nr_inport, int *nr_outport) +{ + struct device_node *ep = NULL; + int in = 0, out = 0; + + do { + ep = of_get_coresight_endpoint(node, ep); + if (!ep) + break; + + if (of_property_read_bool(ep, "slave-mode")) + in++; + else + out++; + + } while (ep); + + *nr_inport = in; + *nr_outport = out; +} + +static int of_coresight_alloc_memory(struct device *dev, + struct coresight_platform_data *pdata) +{ + /* List of output port on this component */ + pdata->outports = devm_kzalloc(dev, pdata->nr_outport * + sizeof(*pdata->outports), + GFP_KERNEL); + if (!pdata->outports) + return -ENOMEM; + + /* Children connected to this component via @outports */ + pdata->child_names = devm_kzalloc(dev, pdata->nr_outport * + sizeof(*pdata->child_names), + GFP_KERNEL); + if (!pdata->child_names) + return -ENOMEM; + + /* Port number on the child this component is connected to */ + pdata->child_ports = devm_kzalloc(dev, pdata->nr_outport * + sizeof(*pdata->child_ports), + GFP_KERNEL); + if (!pdata->child_ports) + return -ENOMEM; + + return 0; +} + +struct coresight_platform_data *of_get_coresight_platform_data( + struct device *dev, struct device_node *node) +{ + int i = 0, ret = 0, cpu; + struct coresight_platform_data *pdata; + struct of_endpoint endpoint, rendpoint; + struct device *rdev; + struct device_node *dn; + struct device_node *ep = NULL; + struct device_node *rparent = NULL; + struct device_node *rport = NULL; + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return ERR_PTR(-ENOMEM); + + /* Use device name as sysfs handle */ + pdata->name = dev_name(dev); + + /* Get the number of input and output port for this component */ + of_coresight_get_ports(node, &pdata->nr_inport, &pdata->nr_outport); + + if (pdata->nr_outport) { + ret = of_coresight_alloc_memory(dev, pdata); + if (ret) + return ERR_PTR(ret); + + /* Iterate through each port to discover topology */ + do { + /* Get a handle on a port */ + ep = of_get_coresight_endpoint(node, ep); + if (!ep) + break; + + /* + * No need to deal with input ports, processing for as + * processing for output ports will deal with them. + */ + if (of_find_property(ep, "slave-mode", NULL)) + continue; + + /* Get a handle on the local endpoint */ + ret = of_graph_parse_endpoint(ep, &endpoint); + + if (ret) + continue; + + /* The local out port number */ + pdata->outports[i] = endpoint.id; + + /* + * Get a handle on the remote port and parent + * attached to it. + */ + rparent = of_graph_get_remote_port_parent(ep); + rport = of_graph_get_remote_port(ep); + + if (!rparent || !rport) + continue; + + if (of_graph_parse_endpoint(rport, &rendpoint)) + continue; + + rdev = of_coresight_get_endpoint_device(rparent); + if (!rdev) + continue; + + pdata->child_names[i] = dev_name(rdev); + pdata->child_ports[i] = rendpoint.id; + + i++; + } while (ep); + } + + /* Affinity defaults to CPU0 */ + pdata->cpu = 0; + dn = of_parse_phandle(node, "cpu", 0); + for (cpu = 0; dn && cpu < nr_cpu_ids; cpu++) { + if (dn == of_get_cpu_node(cpu, NULL)) { + pdata->cpu = cpu; + break; + } + } + + return pdata; +} +EXPORT_SYMBOL_GPL(of_get_coresight_platform_data); -- cgit v0.10.2 From 652594c7dfd9bf6392e3a727bc69d89a2562d953 Mon Sep 17 00:00:00 2001 From: Dexuan Cui Date: Fri, 27 Mar 2015 09:10:08 -0700 Subject: hv: run non-blocking message handlers in the dispatch tasklet A work item in vmbus_connection.work_queue can sleep, waiting for a new host message (usually it is some kind of "completion" message). Currently the new message will be handled in the same workqueue, but since work items in the workqueue is serialized, we actually have no chance to handle the new message if the current work item is sleeping -- as as result, the current work item will hang forever. K. Y. has posted the below fix to resolve the issue: Drivers: hv: vmbus: Perform device register in the per-channel work element Actually we can simplify the fix by directly running non-blocking message handlers in the dispatch tasklet (inspired by K. Y.). This patch is the fundamental change. The following 2 patches will simplify the message offering and rescind-offering handling a lot. Signed-off-by: Dexuan Cui Cc: K. Y. Srinivasan Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/hv/channel_mgmt.c b/drivers/hv/channel_mgmt.c index bb39705..287f07b 100644 --- a/drivers/hv/channel_mgmt.c +++ b/drivers/hv/channel_mgmt.c @@ -33,11 +33,6 @@ #include "hyperv_vmbus.h" -struct vmbus_channel_message_table_entry { - enum vmbus_channel_message_type message_type; - void (*message_handler)(struct vmbus_channel_message_header *msg); -}; - struct vmbus_rescind_work { struct work_struct work; struct vmbus_channel *channel; @@ -827,25 +822,25 @@ static void vmbus_onversion_response( } /* Channel message dispatch table */ -static struct vmbus_channel_message_table_entry +struct vmbus_channel_message_table_entry channel_message_table[CHANNELMSG_COUNT] = { - {CHANNELMSG_INVALID, NULL}, - {CHANNELMSG_OFFERCHANNEL, vmbus_onoffer}, - {CHANNELMSG_RESCIND_CHANNELOFFER, vmbus_onoffer_rescind}, - {CHANNELMSG_REQUESTOFFERS, NULL}, - {CHANNELMSG_ALLOFFERS_DELIVERED, vmbus_onoffers_delivered}, - {CHANNELMSG_OPENCHANNEL, NULL}, - {CHANNELMSG_OPENCHANNEL_RESULT, vmbus_onopen_result}, - {CHANNELMSG_CLOSECHANNEL, NULL}, - {CHANNELMSG_GPADL_HEADER, NULL}, - {CHANNELMSG_GPADL_BODY, NULL}, - {CHANNELMSG_GPADL_CREATED, vmbus_ongpadl_created}, - {CHANNELMSG_GPADL_TEARDOWN, NULL}, - {CHANNELMSG_GPADL_TORNDOWN, vmbus_ongpadl_torndown}, - {CHANNELMSG_RELID_RELEASED, NULL}, - {CHANNELMSG_INITIATE_CONTACT, NULL}, - {CHANNELMSG_VERSION_RESPONSE, vmbus_onversion_response}, - {CHANNELMSG_UNLOAD, NULL}, + {CHANNELMSG_INVALID, 0, NULL}, + {CHANNELMSG_OFFERCHANNEL, 0, vmbus_onoffer}, + {CHANNELMSG_RESCIND_CHANNELOFFER, 0, vmbus_onoffer_rescind}, + {CHANNELMSG_REQUESTOFFERS, 0, NULL}, + {CHANNELMSG_ALLOFFERS_DELIVERED, 1, vmbus_onoffers_delivered}, + {CHANNELMSG_OPENCHANNEL, 0, NULL}, + {CHANNELMSG_OPENCHANNEL_RESULT, 1, vmbus_onopen_result}, + {CHANNELMSG_CLOSECHANNEL, 0, NULL}, + {CHANNELMSG_GPADL_HEADER, 0, NULL}, + {CHANNELMSG_GPADL_BODY, 0, NULL}, + {CHANNELMSG_GPADL_CREATED, 1, vmbus_ongpadl_created}, + {CHANNELMSG_GPADL_TEARDOWN, 0, NULL}, + {CHANNELMSG_GPADL_TORNDOWN, 1, vmbus_ongpadl_torndown}, + {CHANNELMSG_RELID_RELEASED, 0, NULL}, + {CHANNELMSG_INITIATE_CONTACT, 0, NULL}, + {CHANNELMSG_VERSION_RESPONSE, 1, vmbus_onversion_response}, + {CHANNELMSG_UNLOAD, 0, NULL}, }; /* diff --git a/drivers/hv/hyperv_vmbus.h b/drivers/hv/hyperv_vmbus.h index c8e27e0..f40a5a9 100644 --- a/drivers/hv/hyperv_vmbus.h +++ b/drivers/hv/hyperv_vmbus.h @@ -685,6 +685,23 @@ struct vmbus_msginfo { extern struct vmbus_connection vmbus_connection; +enum vmbus_message_handler_type { + /* The related handler can sleep. */ + VMHT_BLOCKING = 0, + + /* The related handler must NOT sleep. */ + VMHT_NON_BLOCKING = 1, +}; + +struct vmbus_channel_message_table_entry { + enum vmbus_channel_message_type message_type; + enum vmbus_message_handler_type handler_type; + void (*message_handler)(struct vmbus_channel_message_header *msg); +}; + +extern struct vmbus_channel_message_table_entry + channel_message_table[CHANNELMSG_COUNT]; + /* General vmbus interface */ struct hv_device *vmbus_device_create(const uuid_le *type, diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c index 8313e25..c85235e 100644 --- a/drivers/hv/vmbus_drv.c +++ b/drivers/hv/vmbus_drv.c @@ -657,21 +657,36 @@ static void vmbus_on_msg_dpc(unsigned long data) 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; + struct vmbus_channel_message_table_entry *entry; struct onmessage_work_context *ctx; while (1) { - if (msg->header.message_type == HVMSG_NONE) { + if (msg->header.message_type == HVMSG_NONE) /* no msg */ break; - } else { + + 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; + } + + entry = &channel_message_table[hdr->msgtype]; + if (entry->handler_type == VMHT_BLOCKING) { ctx = kmalloc(sizeof(*ctx), GFP_ATOMIC); if (ctx == NULL) continue; + 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); +msg_handled: msg->header.message_type = HVMSG_NONE; /* -- cgit v0.10.2 From d43e2fe7da320310834467a3fd87a10adb25a221 Mon Sep 17 00:00:00 2001 From: Dexuan Cui Date: Fri, 27 Mar 2015 09:10:09 -0700 Subject: hv: don't schedule new works in vmbus_onoffer()/vmbus_onoffer_rescind() Since the 2 fucntions can safely run in vmbus_connection.work_queue without hang, we don't need to schedule new work items into the per-channel workqueue. Actally we can even remove the per-channel workqueue now -- we'll do it in the next patch. Signed-off-by: Dexuan Cui Cc: K. Y. Srinivasan Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/hv/channel_mgmt.c b/drivers/hv/channel_mgmt.c index 287f07b..d69864d 100644 --- a/drivers/hv/channel_mgmt.c +++ b/drivers/hv/channel_mgmt.c @@ -23,7 +23,6 @@ #include #include #include -#include #include #include #include @@ -33,11 +32,6 @@ #include "hyperv_vmbus.h" -struct vmbus_rescind_work { - struct work_struct work; - struct vmbus_channel *channel; -}; - /** * vmbus_prep_negotiate_resp() - Create default response for Hyper-V Negotiate message * @icmsghdrp: Pointer to msg header structure @@ -134,20 +128,6 @@ fw_error: EXPORT_SYMBOL_GPL(vmbus_prep_negotiate_resp); -static void vmbus_sc_creation_cb(struct work_struct *work) -{ - struct vmbus_channel *newchannel = container_of(work, - struct vmbus_channel, - work); - struct vmbus_channel *primary_channel = newchannel->primary_channel; - - /* - * On entry sc_creation_callback has been already verified to - * be non-NULL. - */ - primary_channel->sc_creation_callback(newchannel); -} - /* * alloc_channel - Allocate and initialize a vmbus channel object */ @@ -206,40 +186,6 @@ static void free_channel(struct vmbus_channel *channel) queue_work(vmbus_connection.work_queue, &channel->work); } -static void process_rescind_fn(struct work_struct *work) -{ - struct vmbus_rescind_work *rc_work; - struct vmbus_channel *channel; - struct device *dev; - - rc_work = container_of(work, struct vmbus_rescind_work, work); - channel = rc_work->channel; - - /* - * We have already acquired a reference on the channel - * and so it cannot vanish underneath us. - * It is possible (while very unlikely) that we may - * get here while the processing of the initial offer - * is still not complete. Deal with this situation by - * just waiting until the channel is in the correct state. - */ - - while (channel->work.func != release_channel) - msleep(1000); - - if (channel->device_obj) { - dev = get_device(&channel->device_obj->device); - if (dev) { - vmbus_device_unregister(channel->device_obj); - put_device(dev); - } - } else { - hv_process_channel_removal(channel, - channel->offermsg.child_relid); - } - kfree(work); -} - static void percpu_channel_enq(void *arg) { struct vmbus_channel *channel = arg; @@ -302,46 +248,6 @@ void vmbus_free_channels(void) } } -static void vmbus_do_device_register(struct work_struct *work) -{ - struct hv_device *device_obj; - int ret; - unsigned long flags; - struct vmbus_channel *newchannel = container_of(work, - struct vmbus_channel, - work); - - ret = vmbus_device_register(newchannel->device_obj); - if (ret != 0) { - pr_err("unable to add child device object (relid %d)\n", - newchannel->offermsg.child_relid); - spin_lock_irqsave(&vmbus_connection.channel_lock, flags); - list_del(&newchannel->listentry); - device_obj = newchannel->device_obj; - newchannel->device_obj = NULL; - spin_unlock_irqrestore(&vmbus_connection.channel_lock, flags); - - if (newchannel->target_cpu != get_cpu()) { - put_cpu(); - smp_call_function_single(newchannel->target_cpu, - percpu_channel_deq, newchannel, true); - } else { - percpu_channel_deq(newchannel); - put_cpu(); - } - - kfree(device_obj); - if (!newchannel->rescind) { - free_channel(newchannel); - return; - } - } - /* - * The next state for this channel is to be freed. - */ - INIT_WORK(&newchannel->work, release_channel); -} - /* * vmbus_process_offer - Process the offer by creating a channel/device * associated with this offer @@ -410,19 +316,8 @@ static void vmbus_process_offer(struct vmbus_channel *newchannel) newchannel->state = CHANNEL_OPEN_STATE; channel->num_sc++; - if (channel->sc_creation_callback != NULL) { - /* - * We need to invoke the sub-channel creation - * callback; invoke this in a seperate work - * context since we are currently running on - * the global work context in which we handle - * messages from the host. - */ - INIT_WORK(&newchannel->work, - vmbus_sc_creation_cb); - queue_work(newchannel->controlwq, - &newchannel->work); - } + if (channel->sc_creation_callback != NULL) + channel->sc_creation_callback(newchannel); return; } @@ -453,13 +348,13 @@ static void vmbus_process_offer(struct vmbus_channel *newchannel) * Add the new device to the bus. This will kick off device-driver * binding which eventually invokes the device driver's AddDevice() * method. - * Invoke this call on the per-channel work context. - * Until we return from this function, rescind offer message - * cannot be processed as we are running on the global message - * handling work. */ - INIT_WORK(&newchannel->work, vmbus_do_device_register); - queue_work(newchannel->controlwq, &newchannel->work); + if (vmbus_device_register(newchannel->device_obj) != 0) { + pr_err("unable to add child device object (relid %d)\n", + newchannel->offermsg.child_relid); + kfree(newchannel->device_obj); + goto err_deq_chan; + } return; err_deq_chan: @@ -613,31 +508,35 @@ static void vmbus_onoffer_rescind(struct vmbus_channel_message_header *hdr) { struct vmbus_channel_rescind_offer *rescind; struct vmbus_channel *channel; - struct vmbus_rescind_work *rc_work; + unsigned long flags; + struct device *dev; rescind = (struct vmbus_channel_rescind_offer *)hdr; - channel = relid2channel(rescind->child_relid, true); + channel = relid2channel(rescind->child_relid); if (channel == NULL) { hv_process_channel_removal(NULL, rescind->child_relid); return; } - /* - * We have acquired a reference on the channel and have posted - * the rescind state. Perform further cleanup in a work context - * that is different from the global work context in which - * we process messages from the host (we are currently executing - * on that global context. - */ - rc_work = kzalloc(sizeof(struct vmbus_rescind_work), GFP_KERNEL); - if (!rc_work) { - pr_err("Unable to allocate memory for rescind processing "); - return; + spin_lock_irqsave(&channel->lock, flags); + channel->rescind = true; + spin_unlock_irqrestore(&channel->lock, flags); + + if (channel->device_obj) { + /* + * We will have to unregister this device from the + * driver core. + */ + dev = get_device(&channel->device_obj->device); + if (dev) { + vmbus_device_unregister(channel->device_obj); + put_device(dev); + } + } else { + hv_process_channel_removal(channel, + channel->offermsg.child_relid); } - rc_work->channel = channel; - INIT_WORK(&rc_work->work, process_rescind_fn); - schedule_work(&rc_work->work); } /* diff --git a/drivers/hv/connection.c b/drivers/hv/connection.c index 8bcd307..583d7d4 100644 --- a/drivers/hv/connection.c +++ b/drivers/hv/connection.c @@ -270,7 +270,7 @@ static struct vmbus_channel *pcpu_relid2channel(u32 relid) * relid2channel - Get the channel object given its * child relative id (ie channel id) */ -struct vmbus_channel *relid2channel(u32 relid, bool rescind) +struct vmbus_channel *relid2channel(u32 relid) { struct vmbus_channel *channel; struct vmbus_channel *found_channel = NULL; @@ -282,8 +282,6 @@ struct vmbus_channel *relid2channel(u32 relid, bool rescind) list_for_each_entry(channel, &vmbus_connection.chn_list, listentry) { if (channel->offermsg.child_relid == relid) { found_channel = channel; - if (rescind) - found_channel->rescind = true; break; } else if (!list_empty(&channel->sc_list)) { /* @@ -294,8 +292,6 @@ struct vmbus_channel *relid2channel(u32 relid, bool rescind) sc_list); if (cur_sc->offermsg.child_relid == relid) { found_channel = cur_sc; - if (rescind) - found_channel->rescind = true; break; } } diff --git a/drivers/hv/hyperv_vmbus.h b/drivers/hv/hyperv_vmbus.h index f40a5a9..887287a 100644 --- a/drivers/hv/hyperv_vmbus.h +++ b/drivers/hv/hyperv_vmbus.h @@ -715,7 +715,7 @@ void vmbus_device_unregister(struct hv_device *device_obj); /* VmbusChildDeviceDestroy( */ /* struct hv_device *); */ -struct vmbus_channel *relid2channel(u32 relid, bool rescind); +struct vmbus_channel *relid2channel(u32 relid); void vmbus_free_channels(void); -- cgit v0.10.2 From aadc3780f31865edc84c587ab718a33a8eeeb09d Mon Sep 17 00:00:00 2001 From: Dexuan Cui Date: Fri, 27 Mar 2015 09:10:10 -0700 Subject: hv: remove the per-channel workqueue It's not necessary any longer, since we can safely run the blocking message handlers in vmbus_connection.work_queue now. Signed-off-by: Dexuan Cui Cc: K. Y. Srinivasan Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/hv/channel_mgmt.c b/drivers/hv/channel_mgmt.c index d69864d..0eeb1b3 100644 --- a/drivers/hv/channel_mgmt.c +++ b/drivers/hv/channel_mgmt.c @@ -147,43 +147,15 @@ static struct vmbus_channel *alloc_channel(void) INIT_LIST_HEAD(&channel->sc_list); INIT_LIST_HEAD(&channel->percpu_list); - channel->controlwq = alloc_workqueue("hv_vmbus_ctl/%d", WQ_MEM_RECLAIM, - 1, channel->id); - if (!channel->controlwq) { - kfree(channel); - return NULL; - } - return channel; } /* - * release_hannel - Release the vmbus channel object itself - */ -static void release_channel(struct work_struct *work) -{ - struct vmbus_channel *channel = container_of(work, - struct vmbus_channel, - work); - - destroy_workqueue(channel->controlwq); - - kfree(channel); -} - -/* * free_channel - Release the resources used by the vmbus channel object */ static void free_channel(struct vmbus_channel *channel) { - - /* - * We have to release the channel's workqueue/thread in the vmbus's - * workqueue/thread context - * ie we can't destroy ourselves. - */ - INIT_WORK(&channel->work, release_channel); - queue_work(vmbus_connection.work_queue, &channel->work); + kfree(channel); } static void percpu_channel_enq(void *arg) diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index 80e444b..902c37a 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -653,8 +653,6 @@ struct vmbus_channel { struct hv_device *device_obj; - struct work_struct work; - enum vmbus_channel_state state; struct vmbus_channel_offer_channel offermsg; @@ -675,7 +673,6 @@ struct vmbus_channel { struct hv_ring_buffer_info outbound; /* send to parent */ struct hv_ring_buffer_info inbound; /* receive from parent */ spinlock_t inbound_lock; - struct workqueue_struct *controlwq; struct vmbus_close_msg close_msg; -- cgit v0.10.2 From d6cbd2c3a3db437708520c66f285c69ef028ac1f Mon Sep 17 00:00:00 2001 From: Vitaly Kuznetsov Date: Fri, 27 Mar 2015 09:10:11 -0700 Subject: Drivers: hv: hv_balloon: do not online pages in offline blocks Currently we add memory in 128Mb blocks but the request from host can be aligned differently. In such case we add a partially backed block and when this block goes online we skip onlining pages which are not backed (hv_online_page() callback serves this purpose). When we receive next request for the same host add region we online pages which were not backed before with hv_bring_pgs_online(). However, we don't check if the the block in question was onlined and online this tail unconditionally. This is bad as we avoid all online_pages() logic: these pages are not accounted, we don't send notifications (and hv_balloon is not the only receiver of them),... And, first of all, nobody asked as to online these pages. Solve the issue by checking if the last previously backed page was onlined and onlining the tail only in case it was. Signed-off-by: Vitaly Kuznetsov Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/hv/hv_balloon.c b/drivers/hv/hv_balloon.c index 014256a..99afef9 100644 --- a/drivers/hv/hv_balloon.c +++ b/drivers/hv/hv_balloon.c @@ -778,7 +778,17 @@ static unsigned long handle_pg_range(unsigned long pg_start, pgs_ol = has->ha_end_pfn - start_pfn; if (pgs_ol > pfn_cnt) pgs_ol = pfn_cnt; - hv_bring_pgs_online(start_pfn, pgs_ol); + + /* + * Check if the corresponding memory block is already + * online by checking its last previously backed page. + * In case it is we need to bring rest (which was not + * backed previously) online too. + */ + if (start_pfn > has->start_pfn && + !PageReserved(pfn_to_page(start_pfn - 1))) + hv_bring_pgs_online(start_pfn, pgs_ol); + has->covered_end_pfn += pgs_ol; pfn_cnt -= pgs_ol; } -- cgit v0.10.2 From 7fb0e1a65075a871c58cbcf8c877d1f9ae5cd83c Mon Sep 17 00:00:00 2001 From: Vitaly Kuznetsov Date: Fri, 27 Mar 2015 09:10:12 -0700 Subject: Drivers: hv: hv_balloon: eliminate jumps in piecewiese linear floor function Commit 79208c57da53 ("Drivers: hv: hv_balloon: Make adjustments in computing the floor") was inacurate as it introduced a jump in our piecewiese linear 'floor' function: At 2048MB we have: Left limit: 104 + 2048/8 = 360 Right limit: 256 + 2048/16 = 384 (so the right value is 232) We now have to make an adjustment at 8192 boundary: 232 + 8192/16 = 744 512 + 8192/32 = 768 (so the right value is 488) Suggested-by: Laszlo Ersek Signed-off-by: Vitaly Kuznetsov Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/hv/hv_balloon.c b/drivers/hv/hv_balloon.c index 99afef9..4f5323c 100644 --- a/drivers/hv/hv_balloon.c +++ b/drivers/hv/hv_balloon.c @@ -976,8 +976,8 @@ static unsigned long compute_balloon_floor(void) * 128 72 (1/2) * 512 168 (1/4) * 2048 360 (1/8) - * 8192 768 (1/16) - * 32768 1536 (1/32) + * 8192 744 (1/16) + * 32768 1512 (1/32) */ if (totalram_pages < MB2PAGES(128)) min_pages = MB2PAGES(8) + (totalram_pages >> 1); @@ -986,9 +986,9 @@ static unsigned long compute_balloon_floor(void) else if (totalram_pages < MB2PAGES(2048)) min_pages = MB2PAGES(104) + (totalram_pages >> 3); else if (totalram_pages < MB2PAGES(8192)) - min_pages = MB2PAGES(256) + (totalram_pages >> 4); + min_pages = MB2PAGES(232) + (totalram_pages >> 4); else - min_pages = MB2PAGES(512) + (totalram_pages >> 5); + min_pages = MB2PAGES(488) + (totalram_pages >> 5); #undef MB2PAGES return min_pages; } -- cgit v0.10.2 From 0a1a86ac046091d7228c9f3a283dea5be96275dd Mon Sep 17 00:00:00 2001 From: Vitaly Kuznetsov Date: Fri, 27 Mar 2015 09:10:13 -0700 Subject: Drivers: hv: hv_balloon: survive ballooning request with num_pages=0 ... and simplify alloc_balloon_pages() interface by removing redundant alloc_error from it. If we happen to enter balloon_up() with balloon_wrk.num_pages = 0 we will enter infinite 'while (!done)' loop as alloc_balloon_pages() will be always returning 0 and not setting alloc_error. We will also be sending a meaningless message to the host on every iteration. The 'alloc_unit == 1 && alloc_error -> num_ballooned == 0' change and alloc_error elimination requires a special comment. We do alloc_balloon_pages() with 2 different alloc_unit values and there are 4 different alloc_balloon_pages() results, let's check them all. alloc_unit = 512: 1) num_ballooned = 0, alloc_error = 0: we do 'alloc_unit=1' and retry pre- and post-patch. 2) num_ballooned > 0, alloc_error = 0: we check 'num_ballooned == num_pages' and act accordingly, pre- and post-patch. 3) num_ballooned > 0, alloc_error > 0: we report this chunk and remain within the loop, no changes here. 4) num_ballooned = 0, alloc_error > 0: we do 'alloc_unit=1' and retry pre- and post-patch. alloc_unit = 1: 1) num_ballooned = 0, alloc_error = 0: this can happen in two cases: when we passed 'num_pages=0' to alloc_balloon_pages() or when there was no space in bl_resp to place a single response. The second option is not possible as bl_resp is of PAGE_SIZE size and single response 'union dm_mem_page_range' is 8 bytes, but the first one is (in theory, I think that Hyper-V host never places such requests). Pre-patch code loops forever, post-patch code sends a reply with more_pages = 0 and finishes. 2) num_ballooned > 0, alloc_error = 0: we ran out of space in bl_resp, we report partial success and remain within the loop, no changes pre- and post-patch. 3) num_ballooned > 0, alloc_error > 0: pre-patch code finishes, post-patch code does one more try and if there is no progress (we finish with 'num_ballooned = 0') we finish. So we try a bit harder with this patch. 4) num_ballooned = 0, alloc_error > 0: both pre- and post-patch code enter 'more_pages = 0' branch and finish. So this patch has two real effects: 1) We reply with an empty response to 'num_pages=0' request. 2) We try a bit harder on alloc_unit=1 allocations (and reply with an empty tail reply in case we fail). An empty reply should be supported by host as we were able to send it even with pre-patch code when we were not able to allocate a single page. Suggested-by: Laszlo Ersek Signed-off-by: Vitaly Kuznetsov Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/hv/hv_balloon.c b/drivers/hv/hv_balloon.c index 4f5323c..74312c8 100644 --- a/drivers/hv/hv_balloon.c +++ b/drivers/hv/hv_balloon.c @@ -1081,9 +1081,9 @@ static void free_balloon_pages(struct hv_dynmem_device *dm, -static int alloc_balloon_pages(struct hv_dynmem_device *dm, int num_pages, - struct dm_balloon_response *bl_resp, int alloc_unit, - bool *alloc_error) +static int alloc_balloon_pages(struct hv_dynmem_device *dm, int num_pages, + struct dm_balloon_response *bl_resp, + int alloc_unit) { int i = 0; struct page *pg; @@ -1104,11 +1104,8 @@ static int alloc_balloon_pages(struct hv_dynmem_device *dm, int num_pages, __GFP_NOMEMALLOC | __GFP_NOWARN, get_order(alloc_unit << PAGE_SHIFT)); - if (!pg) { - *alloc_error = true; + if (!pg) return i * alloc_unit; - } - dm->num_pages_ballooned += alloc_unit; @@ -1140,7 +1137,6 @@ static void balloon_up(struct work_struct *dummy) struct dm_balloon_response *bl_resp; int alloc_unit; int ret; - bool alloc_error; bool done = false; int i; struct sysinfo val; @@ -1173,18 +1169,15 @@ static void balloon_up(struct work_struct *dummy) num_pages -= num_ballooned; - alloc_error = false; num_ballooned = alloc_balloon_pages(&dm_device, num_pages, - bl_resp, alloc_unit, - &alloc_error); + bl_resp, alloc_unit); if (alloc_unit != 1 && num_ballooned == 0) { alloc_unit = 1; continue; } - if ((alloc_unit == 1 && alloc_error) || - (num_ballooned == num_pages)) { + if (num_ballooned == 0 || num_ballooned == num_pages) { bl_resp->more_pages = 0; done = true; dm_device.state = DM_INITIALIZED; -- cgit v0.10.2 From e1c0d82dab4a4605d3bd1968436f030dfed4a829 Mon Sep 17 00:00:00 2001 From: Haiyang Zhang Date: Fri, 27 Mar 2015 09:10:14 -0700 Subject: hv_vmbus: Add gradually increased delay for retries in vmbus_post_msg() Most of the retries can be done within a millisecond successfully, so we sleep 1ms before the first retry, then gradually increase the retry interval to 2^n with max value of 2048ms. Doing so, we will have shorter overall delay time, because most of the cases succeed within 1-2 attempts. Signed-off-by: Haiyang Zhang Reviewed-by: K. Y. Srinivasan Reviewed-by: Dexuan Cui Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/hv/connection.c b/drivers/hv/connection.c index 583d7d4..b27220a 100644 --- a/drivers/hv/connection.c +++ b/drivers/hv/connection.c @@ -422,6 +422,7 @@ int vmbus_post_msg(void *buffer, size_t buflen) union hv_connection_id conn_id; int ret = 0; int retries = 0; + u32 msec = 1; conn_id.asu32 = 0; conn_id.u.id = VMBUS_MESSAGE_CONNECTION_ID; @@ -431,7 +432,7 @@ int vmbus_post_msg(void *buffer, size_t buflen) * insufficient resources. Retry the operation a couple of * times before giving up. */ - while (retries < 10) { + while (retries < 20) { ret = hv_post_message(conn_id, 1, buffer, buflen); switch (ret) { @@ -454,7 +455,9 @@ int vmbus_post_msg(void *buffer, size_t buflen) } retries++; - msleep(1000); + msleep(msec); + if (msec < 2048) + msec *= 2; } return ret; } -- cgit v0.10.2 From 1d9013f09203c694e2cba478b05afc6484d55180 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Fri, 27 Mar 2015 00:27:57 +0200 Subject: mei: fix mei_poll operation mei_poll returned with POLLIN w/o checking whether the operation has really completed. remove redundant check and locking in amthif specific handler Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/misc/mei/amthif.c b/drivers/misc/mei/amthif.c index 7b6ed0b..3c1fd87 100644 --- a/drivers/misc/mei/amthif.c +++ b/drivers/misc/mei/amthif.c @@ -362,6 +362,18 @@ int mei_amthif_write(struct mei_cl *cl, struct mei_cl_cb *cb) return mei_amthif_run_next_cmd(dev); } +/** + * mei_amthif_poll - the amthif poll function + * + * @dev: the device structure + * @file: pointer to file structure + * @wait: pointer to poll_table structure + * + * Return: poll mask + * + * Locking: called under "dev->device_lock" lock + */ + unsigned int mei_amthif_poll(struct mei_device *dev, struct file *file, poll_table *wait) { @@ -369,19 +381,12 @@ unsigned int mei_amthif_poll(struct mei_device *dev, poll_wait(file, &dev->iamthif_cl.wait, wait); - mutex_lock(&dev->device_lock); - if (!mei_cl_is_connected(&dev->iamthif_cl)) { - - mask = POLLERR; - - } else if (dev->iamthif_state == MEI_IAMTHIF_READ_COMPLETE && - dev->iamthif_file_object == file) { + if (dev->iamthif_state == MEI_IAMTHIF_READ_COMPLETE && + dev->iamthif_file_object == file) { - mask |= (POLLIN | POLLRDNORM); - dev_dbg(dev->dev, "run next amthif cb\n"); + mask |= POLLIN | POLLRDNORM; mei_amthif_run_next_cmd(dev); } - mutex_unlock(&dev->device_lock); return mask; } diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c index b6fec4d..e5aeb6f 100644 --- a/drivers/misc/mei/client.c +++ b/drivers/misc/mei/client.c @@ -1299,7 +1299,7 @@ void mei_cl_complete(struct mei_cl *cl, struct mei_cl_cb *cb) } else if (cb->fop_type == MEI_FOP_READ) { list_add_tail(&cb->list, &cl->rd_completed); if (waitqueue_active(&cl->rx_wait)) - wake_up_interruptible(&cl->rx_wait); + wake_up_interruptible_all(&cl->rx_wait); else mei_cl_bus_rx_event(cl); diff --git a/drivers/misc/mei/main.c b/drivers/misc/mei/main.c index d80867e..a1ec450 100644 --- a/drivers/misc/mei/main.c +++ b/drivers/misc/mei/main.c @@ -542,6 +542,7 @@ static long mei_compat_ioctl(struct file *file, */ static unsigned int mei_poll(struct file *file, poll_table *wait) { + unsigned long req_events = poll_requested_events(wait); struct mei_cl *cl = file->private_data; struct mei_device *dev; unsigned int mask = 0; @@ -558,22 +559,19 @@ static unsigned int mei_poll(struct file *file, poll_table *wait) goto out; } - mutex_unlock(&dev->device_lock); - - - if (cl == &dev->iamthif_cl) - return mei_amthif_poll(dev, file, wait); - - poll_wait(file, &cl->tx_wait, wait); - - mutex_lock(&dev->device_lock); - - if (!mei_cl_is_connected(cl)) { - mask = POLLERR; + if (cl == &dev->iamthif_cl) { + mask = mei_amthif_poll(dev, file, wait); goto out; } - mask |= (POLLIN | POLLRDNORM); + if (req_events & (POLLIN | POLLRDNORM)) { + poll_wait(file, &cl->rx_wait, wait); + + if (!list_empty(&cl->rd_completed)) + mask |= POLLIN | POLLRDNORM; + else + mei_cl_read_start(cl, 0, file); + } out: mutex_unlock(&dev->device_lock); -- cgit v0.10.2 From f3de9b635d93a3d268adda428e1df94091506a42 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Fri, 27 Mar 2015 00:27:58 +0200 Subject: mei: use mei_cl_is_connected consistently Replace open coded check for cl->state !=/== MEI_FILE_CONNECTED with mei_cl_is_connected function. Note that cl->state != MEI_FILE_CONNECTED is not the same as cl->state == MEI_FILE_DISCONNECTED Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/misc/mei/bus.c b/drivers/misc/mei/bus.c index 45896f9..b724a67 100644 --- a/drivers/misc/mei/bus.c +++ b/drivers/misc/mei/bus.c @@ -238,7 +238,7 @@ static ssize_t ___mei_cl_send(struct mei_cl *cl, u8 *buf, size_t length, dev = cl->dev; mutex_lock(&dev->device_lock); - if (cl->state != MEI_FILE_CONNECTED) { + if (!mei_cl_is_connected(cl)) { rets = -ENODEV; goto out; } @@ -474,7 +474,7 @@ int mei_cl_disable_device(struct mei_cl_device *device) mutex_lock(&dev->device_lock); - if (cl->state != MEI_FILE_CONNECTED) { + if (!mei_cl_is_connected(cl)) { dev_err(dev->dev, "Already disconnected"); err = 0; goto out; diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c index e5aeb6f..1e99ef6 100644 --- a/drivers/misc/mei/client.c +++ b/drivers/misc/mei/client.c @@ -876,7 +876,7 @@ int mei_cl_connect(struct mei_cl *cl, struct file *file) mei_secs_to_jiffies(MEI_CL_CONNECT_TIMEOUT)); mutex_lock(&dev->device_lock); - if (cl->state != MEI_FILE_CONNECTED) { + if (!mei_cl_is_connected(cl)) { cl->state = MEI_FILE_DISCONNECTED; /* something went really wrong */ if (!cl->status) diff --git a/drivers/misc/mei/interrupt.c b/drivers/misc/mei/interrupt.c index 3f23629..3f84d2e 100644 --- a/drivers/misc/mei/interrupt.c +++ b/drivers/misc/mei/interrupt.c @@ -110,7 +110,7 @@ int mei_cl_irq_read_msg(struct mei_cl *cl, goto out; } - if (cl->state != MEI_FILE_CONNECTED) { + if (!mei_cl_is_connected(cl)) { cl_dbg(dev, cl, "not connected\n"); cb->status = -ENODEV; goto out; diff --git a/drivers/misc/mei/main.c b/drivers/misc/mei/main.c index a1ec450..29fa88b 100644 --- a/drivers/misc/mei/main.c +++ b/drivers/misc/mei/main.c @@ -107,7 +107,7 @@ static int mei_release(struct inode *inode, struct file *file) rets = mei_amthif_release(dev, file); goto out; } - if (cl->state == MEI_FILE_CONNECTED) { + if (mei_cl_is_connected(cl)) { cl->state = MEI_FILE_DISCONNECTING; cl_dbg(dev, cl, "disconnecting\n"); rets = mei_cl_disconnect(cl); @@ -309,9 +309,8 @@ static ssize_t mei_write(struct file *file, const char __user *ubuf, goto out; } - if (cl->state != MEI_FILE_CONNECTED) { - dev_err(dev->dev, "host client = %d, is not connected to ME client = %d", - cl->host_client_id, cl->me_client_id); + if (!mei_cl_is_connected(cl)) { + cl_err(dev, cl, "is not connected"); rets = -ENODEV; goto out; } @@ -418,7 +417,7 @@ static int mei_ioctl_connect_client(struct file *file, */ if (uuid_le_cmp(data->in_client_uuid, mei_amthif_guid) == 0) { dev_dbg(dev->dev, "FW Client is amthi\n"); - if (dev->iamthif_cl.state != MEI_FILE_CONNECTED) { + if (!mei_cl_is_connected(&dev->iamthif_cl)) { rets = -ENODEV; goto end; } @@ -554,7 +553,9 @@ static unsigned int mei_poll(struct file *file, poll_table *wait) mutex_lock(&dev->device_lock); - if (!mei_cl_is_connected(cl)) { + + if (dev->dev_state != MEI_DEV_ENABLED || + !mei_cl_is_connected(cl)) { mask = POLLERR; goto out; } diff --git a/drivers/misc/mei/wd.c b/drivers/misc/mei/wd.c index ac1e6235..2725f86 100644 --- a/drivers/misc/mei/wd.c +++ b/drivers/misc/mei/wd.c @@ -160,9 +160,10 @@ int mei_wd_send(struct mei_device *dev) */ int mei_wd_stop(struct mei_device *dev) { + struct mei_cl *cl = &dev->wd_cl; int ret; - if (dev->wd_cl.state != MEI_FILE_CONNECTED || + if (!mei_cl_is_connected(cl) || dev->wd_state != MEI_WD_RUNNING) return 0; @@ -170,7 +171,7 @@ int mei_wd_stop(struct mei_device *dev) dev->wd_state = MEI_WD_STOPPING; - ret = mei_cl_flow_ctrl_creds(&dev->wd_cl); + ret = mei_cl_flow_ctrl_creds(cl); if (ret < 0) goto err; @@ -211,13 +212,16 @@ err: */ static int mei_wd_ops_start(struct watchdog_device *wd_dev) { - int err = -ENODEV; struct mei_device *dev; + struct mei_cl *cl; + int err = -ENODEV; dev = watchdog_get_drvdata(wd_dev); if (!dev) return -ENODEV; + cl = &dev->wd_cl; + mutex_lock(&dev->device_lock); if (dev->dev_state != MEI_DEV_ENABLED) { @@ -226,8 +230,8 @@ static int mei_wd_ops_start(struct watchdog_device *wd_dev) goto end_unlock; } - if (dev->wd_cl.state != MEI_FILE_CONNECTED) { - dev_dbg(dev->dev, "MEI Driver is not connected to Watchdog Client\n"); + if (!mei_cl_is_connected(cl)) { + cl_dbg(dev, cl, "MEI Driver is not connected to Watchdog Client\n"); goto end_unlock; } @@ -282,8 +286,8 @@ static int mei_wd_ops_ping(struct watchdog_device *wd_dev) mutex_lock(&dev->device_lock); - if (cl->state != MEI_FILE_CONNECTED) { - dev_err(dev->dev, "wd: not connected.\n"); + if (!mei_cl_is_connected(cl)) { + cl_err(dev, cl, "wd: not connected.\n"); ret = -ENODEV; goto end; } -- cgit v0.10.2 From 6a84d63d22a0ac79ab422b69ef2b4d75002c5641 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Fri, 27 Mar 2015 00:27:59 +0200 Subject: mei: replace check for connection instead of transitioning The function mei_cl_is_transitioning is just opposite of mei_cl_is_connected. What we actually wanted to check is if we lost connection so we can discard the check for transition and check for 'not connected' Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/misc/mei/bus.c b/drivers/misc/mei/bus.c index b724a67..4cf38c3 100644 --- a/drivers/misc/mei/bus.c +++ b/drivers/misc/mei/bus.c @@ -302,7 +302,7 @@ ssize_t __mei_cl_recv(struct mei_cl *cl, u8 *buf, size_t length) if (wait_event_interruptible(cl->rx_wait, (!list_empty(&cl->rd_completed)) || - mei_cl_is_transitioning(cl))) { + (!mei_cl_is_connected(cl)))) { if (signal_pending(current)) return -EINTR; @@ -311,7 +311,7 @@ ssize_t __mei_cl_recv(struct mei_cl *cl, u8 *buf, size_t length) mutex_lock(&dev->device_lock); - if (mei_cl_is_transitioning(cl)) { + if (!mei_cl_is_connected(cl)) { rets = -EBUSY; goto out; } diff --git a/drivers/misc/mei/client.h b/drivers/misc/mei/client.h index 7800d1b..0a39e5d 100644 --- a/drivers/misc/mei/client.h +++ b/drivers/misc/mei/client.h @@ -90,16 +90,18 @@ int mei_cl_flow_ctrl_reduce(struct mei_cl *cl); /* * MEI input output function prototype */ + +/** + * mei_cl_is_connected - host client is connected + * + * @cl: host clinet + * + * Return: true if the host clinet is connected + */ static inline bool mei_cl_is_connected(struct mei_cl *cl) { return cl->state == MEI_FILE_CONNECTED; } -static inline bool mei_cl_is_transitioning(struct mei_cl *cl) -{ - return MEI_FILE_INITIALIZING == cl->state || - MEI_FILE_DISCONNECTED == cl->state || - MEI_FILE_DISCONNECTING == cl->state; -} bool mei_cl_is_other_connecting(struct mei_cl *cl); int mei_cl_disconnect(struct mei_cl *cl); diff --git a/drivers/misc/mei/main.c b/drivers/misc/mei/main.c index 29fa88b..7f77f39 100644 --- a/drivers/misc/mei/main.c +++ b/drivers/misc/mei/main.c @@ -203,7 +203,7 @@ static ssize_t mei_read(struct file *file, char __user *ubuf, if (wait_event_interruptible(cl->rx_wait, (!list_empty(&cl->rd_completed)) || - mei_cl_is_transitioning(cl))) { + (!mei_cl_is_connected(cl)))) { if (signal_pending(current)) return -EINTR; @@ -211,7 +211,7 @@ static ssize_t mei_read(struct file *file, char __user *ubuf, } mutex_lock(&dev->device_lock); - if (mei_cl_is_transitioning(cl)) { + if (!mei_cl_is_connected(cl)) { rets = -EBUSY; goto out; } -- cgit v0.10.2 From ba0c444153889a9b672974d85a4a57a8eeb49fe3 Mon Sep 17 00:00:00 2001 From: Vitaly Kuznetsov Date: Tue, 31 Mar 2015 11:16:40 -0700 Subject: Drivers: hv: hv_balloon: correctly handle val.freeram Signed-off-by: Vitaly Kuznetsov Reviewed-by: Laszlo Ersek Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/hv/hv_balloon.c b/drivers/hv/hv_balloon.c index 74312c8..4052ad8 100644 --- a/drivers/hv/hv_balloon.c +++ b/drivers/hv/hv_balloon.c @@ -1155,7 +1155,7 @@ static void balloon_up(struct work_struct *dummy) floor = compute_balloon_floor(); /* Refuse to balloon below the floor, keep the 2M granularity. */ - if (val.freeram - num_pages < floor) { + if (val.freeram < num_pages || val.freeram - num_pages < floor) { num_pages = val.freeram > floor ? (val.freeram - floor) : 0; num_pages -= num_pages % PAGES_IN_2M; } -- cgit v0.10.2 From 797f88c987b02a8de8d4fac94ec2877b92ec35a2 Mon Sep 17 00:00:00 2001 From: Vitaly Kuznetsov Date: Tue, 31 Mar 2015 11:16:41 -0700 Subject: Drivers: hv: hv_balloon: correctly handle num_pages>INT_MAX case balloon_wrk.num_pages is __u32 and it comes from host in struct dm_balloon where it is also __u32. We, however, use 'int' in balloon_up() and in case we happen to receive num_pages>INT_MAX request we'll end up allocating zero pages as 'num_pages < alloc_unit' check in alloc_balloon_pages() will pass. Change num_pages type to unsigned int. In real life ballooning request come with num_pages in [512, 32768] range so this is more a future-proof/cleanup. Reported-by: Laszlo Ersek Signed-off-by: Vitaly Kuznetsov Reviewed-by: Laszlo Ersek Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/hv/hv_balloon.c b/drivers/hv/hv_balloon.c index 4052ad8..cb5b7dc 100644 --- a/drivers/hv/hv_balloon.c +++ b/drivers/hv/hv_balloon.c @@ -1081,11 +1081,12 @@ static void free_balloon_pages(struct hv_dynmem_device *dm, -static int alloc_balloon_pages(struct hv_dynmem_device *dm, int num_pages, - struct dm_balloon_response *bl_resp, - int alloc_unit) +static unsigned int alloc_balloon_pages(struct hv_dynmem_device *dm, + unsigned int num_pages, + struct dm_balloon_response *bl_resp, + int alloc_unit) { - int i = 0; + unsigned int i = 0; struct page *pg; if (num_pages < alloc_unit) @@ -1132,8 +1133,8 @@ static int alloc_balloon_pages(struct hv_dynmem_device *dm, int num_pages, static void balloon_up(struct work_struct *dummy) { - int num_pages = dm_device.balloon_wrk.num_pages; - int num_ballooned = 0; + unsigned int num_pages = dm_device.balloon_wrk.num_pages; + unsigned int num_ballooned = 0; struct dm_balloon_response *bl_resp; int alloc_unit; int ret; -- cgit v0.10.2 From cdcd6f824ecb6b5e5fd6729c2552c1254012c3ca Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Fri, 27 Mar 2015 15:39:43 +0100 Subject: lis3lv02d: DT: use s32 to support negative values MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit st,axis-{x,y,z} can be negative to imply inverted axis. Apart from that the minimal and maximal threshold may be negative. Signed-off-by: Sebastian Reichel Reviewed-by: Éric Piel Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/misc/lis3lv02d/lis3lv02d.c b/drivers/misc/lis3lv02d/lis3lv02d.c index 3ef4627..d2b0968 100644 --- a/drivers/misc/lis3lv02d/lis3lv02d.c +++ b/drivers/misc/lis3lv02d/lis3lv02d.c @@ -950,6 +950,7 @@ int lis3lv02d_init_dt(struct lis3lv02d *lis3) struct lis3lv02d_platform_data *pdata; struct device_node *np = lis3->of_node; u32 val; + s32 sval; if (!lis3->of_node) return 0; @@ -1054,29 +1055,29 @@ int lis3lv02d_init_dt(struct lis3lv02d *lis3) if (of_get_property(np, "st,hipass2-disable", NULL)) pdata->hipass_ctrl |= LIS3_HIPASS2_DISABLE; - if (of_get_property(np, "st,axis-x", &val)) - pdata->axis_x = val; - if (of_get_property(np, "st,axis-y", &val)) - pdata->axis_y = val; - if (of_get_property(np, "st,axis-z", &val)) - pdata->axis_z = val; + if (of_property_read_s32(np, "st,axis-x", &sval) == 0) + pdata->axis_x = sval; + if (of_property_read_s32(np, "st,axis-y", &sval) == 0) + pdata->axis_y = sval; + if (of_property_read_s32(np, "st,axis-z", &sval) == 0) + pdata->axis_z = sval; if (of_get_property(np, "st,default-rate", NULL)) pdata->default_rate = val; - if (of_get_property(np, "st,min-limit-x", &val)) - pdata->st_min_limits[0] = val; - if (of_get_property(np, "st,min-limit-y", &val)) - pdata->st_min_limits[1] = val; - if (of_get_property(np, "st,min-limit-z", &val)) - pdata->st_min_limits[2] = val; - - if (of_get_property(np, "st,max-limit-x", &val)) - pdata->st_max_limits[0] = val; - if (of_get_property(np, "st,max-limit-y", &val)) - pdata->st_max_limits[1] = val; - if (of_get_property(np, "st,max-limit-z", &val)) - pdata->st_max_limits[2] = val; + if (of_property_read_s32(np, "st,min-limit-x", &sval) == 0) + pdata->st_min_limits[0] = sval; + if (of_property_read_s32(np, "st,min-limit-y", &sval) == 0) + pdata->st_min_limits[1] = sval; + if (of_property_read_s32(np, "st,min-limit-z", &sval) == 0) + pdata->st_min_limits[2] = sval; + + if (of_property_read_s32(np, "st,max-limit-x", &sval) == 0) + pdata->st_max_limits[0] = sval; + if (of_property_read_s32(np, "st,max-limit-y", &sval) == 0) + pdata->st_max_limits[1] = sval; + if (of_property_read_s32(np, "st,max-limit-z", &sval) == 0) + pdata->st_max_limits[2] = sval; lis3->pdata = pdata; -- cgit v0.10.2 From c5131a373613713fccd3b7a377d957391c498f21 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Fri, 27 Mar 2015 15:39:44 +0100 Subject: lis3lv02d: DT: add wakeup unit 2 and wakeup threshold MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds support for the the wakeup threshold and support for the second wakeup unit to the DT based setup. Signed-off-by: Sebastian Reichel Reviewed-by: Éric Piel Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/misc/lis3lv02d/lis3lv02d.c b/drivers/misc/lis3lv02d/lis3lv02d.c index d2b0968..4739689 100644 --- a/drivers/misc/lis3lv02d/lis3lv02d.c +++ b/drivers/misc/lis3lv02d/lis3lv02d.c @@ -1032,6 +1032,23 @@ int lis3lv02d_init_dt(struct lis3lv02d *lis3) pdata->wakeup_flags |= LIS3_WAKEUP_Z_LO; if (of_get_property(np, "st,wakeup-z-hi", NULL)) pdata->wakeup_flags |= LIS3_WAKEUP_Z_HI; + if (of_get_property(np, "st,wakeup-threshold", &val)) + pdata->wakeup_thresh = val; + + if (of_get_property(np, "st,wakeup2-x-lo", NULL)) + pdata->wakeup_flags2 |= LIS3_WAKEUP_X_LO; + if (of_get_property(np, "st,wakeup2-x-hi", NULL)) + pdata->wakeup_flags2 |= LIS3_WAKEUP_X_HI; + if (of_get_property(np, "st,wakeup2-y-lo", NULL)) + pdata->wakeup_flags2 |= LIS3_WAKEUP_Y_LO; + if (of_get_property(np, "st,wakeup2-y-hi", NULL)) + pdata->wakeup_flags2 |= LIS3_WAKEUP_Y_HI; + if (of_get_property(np, "st,wakeup2-z-lo", NULL)) + pdata->wakeup_flags2 |= LIS3_WAKEUP_Z_LO; + if (of_get_property(np, "st,wakeup2-z-hi", NULL)) + pdata->wakeup_flags2 |= LIS3_WAKEUP_Z_HI; + if (of_get_property(np, "st,wakeup2-threshold", &val)) + pdata->wakeup_thresh2 = val; if (!of_property_read_u32(np, "st,highpass-cutoff-hz", &val)) { switch (val) { -- cgit v0.10.2 From 21e8681862a544517c1c9d6515770efe3897e326 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Fri, 27 Mar 2015 15:39:45 +0100 Subject: Documentation: DT: lis302: update wakeup binding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This updated the documentation of the DT binding to describe the added wakeup threshold and second wakeup engine. It also adds a note, that the axis values may be negative. Signed-off-by: Sebastian Reichel Reviewed-by: Éric Piel Signed-off-by: Greg Kroah-Hartman diff --git a/Documentation/devicetree/bindings/misc/lis302.txt b/Documentation/devicetree/bindings/misc/lis302.txt index 6def86f..2a19bff 100644 --- a/Documentation/devicetree/bindings/misc/lis302.txt +++ b/Documentation/devicetree/bindings/misc/lis302.txt @@ -46,11 +46,18 @@ Optional properties for all bus drivers: interrupt 2 - st,wakeup-{x,y,z}-{lo,hi}: set wakeup condition on x/y/z axis for upper/lower limit + - st,wakeup-threshold: set wakeup threshold + - st,wakeup2-{x,y,z}-{lo,hi}: set wakeup condition on x/y/z axis for + upper/lower limit for second wakeup + engine. + - st,wakeup2-threshold: set wakeup threshold for second wakeup + engine. - st,highpass-cutoff-hz=: 1, 2, 4 or 8 for 1Hz, 2Hz, 4Hz or 8Hz of highpass cut-off frequency - st,hipass{1,2}-disable: disable highpass 1/2. - st,default-rate=: set the default rate - - st,axis-{x,y,z}=: set the axis to map to the three coordinates + - st,axis-{x,y,z}=: set the axis to map to the three coordinates. + Negative values can be used for inverted axis. - st,{min,max}-limit-{x,y,z} set the min/max limits for x/y/z axis (used by self-test) -- cgit v0.10.2 From 1ac4e6fee41d6534b6e54dcbed381590e242bdcb Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Fri, 27 Mar 2015 15:39:46 +0100 Subject: DTS: ARM: OMAP3-N900: Add lis3lv02d support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds support for the N900's accelerometer to the Nokia N900 DTS file. Signed-off-by: Sebastian Reichel Acked-by: Tony Lindgren Reviewed-by: Éric Piel Signed-off-by: Greg Kroah-Hartman diff --git a/arch/arm/boot/dts/omap3-n900.dts b/arch/arm/boot/dts/omap3-n900.dts index db80f9d..2cab149 100644 --- a/arch/arm/boot/dts/omap3-n900.dts +++ b/arch/arm/boot/dts/omap3-n900.dts @@ -609,6 +609,58 @@ pinctrl-0 = <&i2c3_pins>; clock-frequency = <400000>; + + lis302dl: lis3lv02d@1d { + compatible = "st,lis3lv02d"; + reg = <0x1d>; + + Vdd-supply = <&vaux1>; + Vdd_IO-supply = <&vio>; + + interrupt-parent = <&gpio6>; + interrupts = <21 20>; /* 181 and 180 */ + + /* click flags */ + st,click-single-x; + st,click-single-y; + st,click-single-z; + + /* Limits are 0.5g * value */ + st,click-threshold-x = <8>; + st,click-threshold-y = <8>; + st,click-threshold-z = <10>; + + /* Click must be longer than time limit */ + st,click-time-limit = <9>; + + /* Kind of debounce filter */ + st,click-latency = <50>; + + /* Interrupt line 2 for click detection */ + st,irq2-click; + + st,wakeup-x-hi; + st,wakeup-y-hi; + st,wakeup-threshold = <(800/18)>; /* millig-value / 18 to get HW values */ + + st,wakeup2-z-hi; + st,wakeup2-threshold = <(900/18)>; /* millig-value / 18 to get HW values */ + + st,hipass1-disable; + st,hipass2-disable; + + st,axis-x = <1>; /* LIS3_DEV_X */ + st,axis-y = <(-2)>; /* LIS3_INV_DEV_Y */ + st,axis-z = <(-3)>; /* LIS3_INV_DEV_Z */ + + st,min-limit-x = <(-32)>; + st,min-limit-y = <3>; + st,min-limit-z = <3>; + + st,max-limit-x = <(-3)>; + st,max-limit-y = <32>; + st,max-limit-z = <32>; + }; }; &mmc1 { -- cgit v0.10.2 From ea5505fabd3b59608750bfd3721d0f8bc5c8b0bb Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Mon, 13 Apr 2015 13:56:20 +0300 Subject: mei: trace: remove unused TRACE_SYSTEM_STRING fix warning: include/trace/ftrace.h:28:0: note: this is the location of the previous definition ^ In file included from include/trace/define_trace.h:90:0, from drivers/misc/mei/mei-trace.h:76, from drivers/misc/mei/mei-trace.c:21: include/trace/ftrace.h:28:0: warning: "TRACE_SYSTEM_STRING" redefined Cc: Stephen Rothwell Reported-by: Stephen Rothwell Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/misc/mei/mei-trace.h b/drivers/misc/mei/mei-trace.h index 5f4e1a1..47e1bc6 100644 --- a/drivers/misc/mei/mei-trace.h +++ b/drivers/misc/mei/mei-trace.h @@ -24,9 +24,7 @@ #include #undef TRACE_SYSTEM - #define TRACE_SYSTEM mei -#define TRACE_SYSTEM_STRING __stringify(TRACE_SYSTEM) TRACE_EVENT(mei_reg_read, TP_PROTO(const struct device *dev, const char *reg, u32 offs, u32 val), -- cgit v0.10.2