From 918da95176beb743ff47b3bbfb0a4ee420658a95 Mon Sep 17 00:00:00 2001 From: Andrew Jeffery Date: Tue, 20 Feb 2018 14:48:33 +1030 Subject: fsi: gpio: Trace busy count An observation from trace output of the existing FSI tracepoints was that the remote device was sometimes reporting as busy. Add a new tracepoint reporting the busy count in order to get a better grip on how often this is the case. Signed-off-by: Andrew Jeffery Acked-by: Eddie James Signed-off-by: Benjamin Herrenschmidt Tested-by: Joel Stanley --- drivers/fsi/fsi-master-gpio.c | 3 +++ include/trace/events/fsi_master_gpio.h | 16 ++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/drivers/fsi/fsi-master-gpio.c b/drivers/fsi/fsi-master-gpio.c index 3f487449a277..2a49b167effe 100644 --- a/drivers/fsi/fsi-master-gpio.c +++ b/drivers/fsi/fsi-master-gpio.c @@ -401,6 +401,9 @@ retry: break; } + if (busy_count > 0) + trace_fsi_master_gpio_poll_response_busy(master, busy_count); + /* Clock the slave enough to be ready for next operation */ clock_zeros(master, FSI_GPIO_PRIME_SLAVE_CLOCKS); return rc; diff --git a/include/trace/events/fsi_master_gpio.h b/include/trace/events/fsi_master_gpio.h index f95cf3264bf9..33e928c5acf3 100644 --- a/include/trace/events/fsi_master_gpio.h +++ b/include/trace/events/fsi_master_gpio.h @@ -64,6 +64,22 @@ TRACE_EVENT(fsi_master_gpio_break, ) ); + +TRACE_EVENT(fsi_master_gpio_poll_response_busy, + TP_PROTO(const struct fsi_master_gpio *master, int busy), + TP_ARGS(master, busy), + TP_STRUCT__entry( + __field(int, master_idx) + __field(int, busy) + ), + TP_fast_assign( + __entry->master_idx = master->master.idx; + __entry->busy = busy; + ), + TP_printk("fsi-gpio%d: device reported busy %d times", + __entry->master_idx, __entry->busy) +); + #endif /* _TRACE_FSI_MASTER_GPIO_H */ #include -- cgit v1.2.3 From e212ab47bd029466ad758a24831e3046a34ebda1 Mon Sep 17 00:00:00 2001 From: Andrew Jeffery Date: Tue, 20 Feb 2018 14:48:43 +1030 Subject: fsi: gpio: Remove unused 'id' variable Signed-off-by: Andrew Jeffery Signed-off-by: Benjamin Herrenschmidt Tested-by: Joel Stanley --- drivers/fsi/fsi-master-gpio.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/fsi/fsi-master-gpio.c b/drivers/fsi/fsi-master-gpio.c index 2a49b167effe..20b334f1827d 100644 --- a/drivers/fsi/fsi-master-gpio.c +++ b/drivers/fsi/fsi-master-gpio.c @@ -270,7 +270,7 @@ static int read_one_response(struct fsi_master_gpio *master, uint8_t data_size, struct fsi_gpio_msg *msgp, uint8_t *tagp) { struct fsi_gpio_msg msg; - uint8_t id, tag; + uint8_t tag; uint32_t crc; int i; @@ -295,7 +295,6 @@ static int read_one_response(struct fsi_master_gpio *master, /* Read slave ID & response tag */ serial_in(master, &msg, 4); - id = (msg.msg >> FSI_GPIO_MSG_RESPID_SIZE) & 0x3; tag = msg.msg & 0x3; /* If we have an ACK and we're expecting data, clock the data in too */ -- cgit v1.2.3 From e5538139e043bcae133da1e55f02b0646eb601fa Mon Sep 17 00:00:00 2001 From: Jeremy Kerr Date: Tue, 20 Feb 2018 14:48:44 +1030 Subject: fsi: gpio: Use a mutex to protect transfers Reduce time spent with interrupts disabled by limiting the critical sections to bitbanging FSI symbols. We only need to ensure exclusive use of the bus for an entire transfer, not that the transfer be performed in atomic context. Signed-off-by: Jeremy Kerr Signed-off-by: Benjamin Herrenschmidt Tested-by: Joel Stanley --- drivers/fsi/fsi-master-gpio.c | 86 ++++++++++++++++++++++++++++++++----------- 1 file changed, 64 insertions(+), 22 deletions(-) diff --git a/drivers/fsi/fsi-master-gpio.c b/drivers/fsi/fsi-master-gpio.c index 20b334f1827d..4295a46780cb 100644 --- a/drivers/fsi/fsi-master-gpio.c +++ b/drivers/fsi/fsi-master-gpio.c @@ -54,7 +54,8 @@ struct fsi_master_gpio { struct fsi_master master; struct device *dev; - spinlock_t cmd_lock; /* Lock for commands */ + struct mutex cmd_lock; /* mutex for command ordering */ + spinlock_t bit_lock; /* lock for clocking bits out */ struct gpio_desc *gpio_clk; struct gpio_desc *gpio_data; struct gpio_desc *gpio_trans; /* Voltage translator */ @@ -270,10 +271,13 @@ static int read_one_response(struct fsi_master_gpio *master, uint8_t data_size, struct fsi_gpio_msg *msgp, uint8_t *tagp) { struct fsi_gpio_msg msg; - uint8_t tag; + unsigned long flags; uint32_t crc; + uint8_t tag; int i; + spin_lock_irqsave(&master->bit_lock, flags); + /* wait for the start bit */ for (i = 0; i < FSI_GPIO_MTOE_COUNT; i++) { msg.bits = 0; @@ -286,6 +290,7 @@ static int read_one_response(struct fsi_master_gpio *master, dev_dbg(master->dev, "Master time out waiting for response\n"); fsi_master_gpio_error(master, FSI_GPIO_MTOE); + spin_unlock_irqrestore(&master->bit_lock, flags); return -EIO; } @@ -304,6 +309,8 @@ static int read_one_response(struct fsi_master_gpio *master, /* read CRC */ serial_in(master, &msg, FSI_GPIO_CRC_SIZE); + spin_unlock_irqrestore(&master->bit_lock, flags); + /* we have a whole message now; check CRC */ crc = crc4(0, 1, 1); crc = crc4(crc, msg.msg, msg.bits); @@ -324,12 +331,16 @@ static int read_one_response(struct fsi_master_gpio *master, static int issue_term(struct fsi_master_gpio *master, uint8_t slave) { struct fsi_gpio_msg cmd; + unsigned long flags; uint8_t tag; int rc; build_term_command(&cmd, slave); + + spin_lock_irqsave(&master->bit_lock, flags); serial_out(master, &cmd); echo_delay(master); + spin_unlock_irqrestore(&master->bit_lock, flags); rc = read_one_response(master, 0, NULL, &tag); if (rc < 0) { @@ -349,6 +360,7 @@ static int poll_for_response(struct fsi_master_gpio *master, { struct fsi_gpio_msg response, cmd; int busy_count = 0, rc, i; + unsigned long flags; uint8_t tag; uint8_t *data_byte = data; @@ -377,15 +389,20 @@ retry: * d-poll, not indicated in the hardware protocol * spec. < 20 clocks causes slave to hang, 21 ok. */ - clock_zeros(master, FSI_GPIO_DPOLL_CLOCKS); if (busy_count++ < FSI_GPIO_MAX_BUSY) { build_dpoll_command(&cmd, slave); + spin_lock_irqsave(&master->bit_lock, flags); + clock_zeros(master, FSI_GPIO_DPOLL_CLOCKS); serial_out(master, &cmd); echo_delay(master); + spin_unlock_irqrestore(&master->bit_lock, flags); goto retry; } dev_warn(master->dev, "ERR slave is stuck in busy state, issuing TERM\n"); + spin_lock_irqsave(&master->bit_lock, flags); + clock_zeros(master, FSI_GPIO_DPOLL_CLOCKS); + spin_unlock_irqrestore(&master->bit_lock, flags); issue_term(master, slave); rc = -EIO; break; @@ -404,27 +421,42 @@ retry: trace_fsi_master_gpio_poll_response_busy(master, busy_count); /* Clock the slave enough to be ready for next operation */ + spin_lock_irqsave(&master->bit_lock, flags); clock_zeros(master, FSI_GPIO_PRIME_SLAVE_CLOCKS); + spin_unlock_irqrestore(&master->bit_lock, flags); return rc; } -static int fsi_master_gpio_xfer(struct fsi_master_gpio *master, uint8_t slave, - struct fsi_gpio_msg *cmd, size_t resp_len, void *resp) +static int send_request(struct fsi_master_gpio *master, + struct fsi_gpio_msg *cmd) { unsigned long flags; - int rc; - - spin_lock_irqsave(&master->cmd_lock, flags); + spin_lock_irqsave(&master->bit_lock, flags); if (master->external_mode) { - spin_unlock_irqrestore(&master->cmd_lock, flags); + spin_unlock_irqrestore(&master->bit_lock, flags); return -EBUSY; } serial_out(master, cmd); echo_delay(master); - rc = poll_for_response(master, slave, resp_len, resp); - spin_unlock_irqrestore(&master->cmd_lock, flags); + spin_unlock_irqrestore(&master->bit_lock, flags); + + return 0; +} + +static int fsi_master_gpio_xfer(struct fsi_master_gpio *master, uint8_t slave, + struct fsi_gpio_msg *cmd, size_t resp_len, void *resp) +{ + int rc; + + mutex_lock(&master->cmd_lock); + + rc = send_request(master, cmd); + if (!rc) + rc = poll_for_response(master, slave, resp_len, resp); + + mutex_unlock(&master->cmd_lock); return rc; } @@ -478,11 +510,14 @@ static int fsi_master_gpio_break(struct fsi_master *_master, int link) trace_fsi_master_gpio_break(master); - spin_lock_irqsave(&master->cmd_lock, flags); + mutex_lock(&master->cmd_lock); if (master->external_mode) { - spin_unlock_irqrestore(&master->cmd_lock, flags); + mutex_unlock(&master->cmd_lock); return -EBUSY; } + + spin_lock_irqsave(&master->bit_lock, flags); + set_sda_output(master, 1); sda_out(master, 1); clock_toggle(master, FSI_PRE_BREAK_CLOCKS); @@ -491,7 +526,9 @@ static int fsi_master_gpio_break(struct fsi_master *_master, int link) echo_delay(master); sda_out(master, 1); clock_toggle(master, FSI_POST_BREAK_CLOCKS); - spin_unlock_irqrestore(&master->cmd_lock, flags); + + spin_unlock_irqrestore(&master->bit_lock, flags); + mutex_unlock(&master->cmd_lock); /* Wait for logic reset to take effect */ udelay(200); @@ -501,6 +538,8 @@ static int fsi_master_gpio_break(struct fsi_master *_master, int link) static void fsi_master_gpio_init(struct fsi_master_gpio *master) { + unsigned long flags; + gpiod_direction_output(master->gpio_mux, 1); gpiod_direction_output(master->gpio_trans, 1); gpiod_direction_output(master->gpio_enable, 1); @@ -508,7 +547,9 @@ static void fsi_master_gpio_init(struct fsi_master_gpio *master) gpiod_direction_output(master->gpio_data, 1); /* todo: evaluate if clocks can be reduced */ + spin_lock_irqsave(&master->bit_lock, flags); clock_zeros(master, FSI_INIT_CLOCKS); + spin_unlock_irqrestore(&master->bit_lock, flags); } static void fsi_master_gpio_init_external(struct fsi_master_gpio *master) @@ -523,18 +564,17 @@ static void fsi_master_gpio_init_external(struct fsi_master_gpio *master) static int fsi_master_gpio_link_enable(struct fsi_master *_master, int link) { struct fsi_master_gpio *master = to_fsi_master_gpio(_master); - unsigned long flags; int rc = -EBUSY; if (link != 0) return -ENODEV; - spin_lock_irqsave(&master->cmd_lock, flags); + mutex_lock(&master->cmd_lock); if (!master->external_mode) { gpiod_set_value(master->gpio_enable, 1); rc = 0; } - spin_unlock_irqrestore(&master->cmd_lock, flags); + mutex_unlock(&master->cmd_lock); return rc; } @@ -552,7 +592,7 @@ static ssize_t external_mode_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct fsi_master_gpio *master = dev_get_drvdata(dev); - unsigned long flags, val; + unsigned long val; bool external_mode; int err; @@ -562,10 +602,10 @@ static ssize_t external_mode_store(struct device *dev, external_mode = !!val; - spin_lock_irqsave(&master->cmd_lock, flags); + mutex_lock(&master->cmd_lock); if (external_mode == master->external_mode) { - spin_unlock_irqrestore(&master->cmd_lock, flags); + mutex_unlock(&master->cmd_lock); return count; } @@ -574,7 +614,8 @@ static ssize_t external_mode_store(struct device *dev, fsi_master_gpio_init_external(master); else fsi_master_gpio_init(master); - spin_unlock_irqrestore(&master->cmd_lock, flags); + + mutex_unlock(&master->cmd_lock); fsi_master_rescan(&master->master); @@ -642,7 +683,8 @@ static int fsi_master_gpio_probe(struct platform_device *pdev) master->master.send_break = fsi_master_gpio_break; master->master.link_enable = fsi_master_gpio_link_enable; platform_set_drvdata(pdev, master); - spin_lock_init(&master->cmd_lock); + spin_lock_init(&master->bit_lock); + mutex_init(&master->cmd_lock); fsi_master_gpio_init(master); -- cgit v1.2.3 From 5d0d16f135c0cb7a12e417cbe12b271524ce4d40 Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Tue, 8 May 2018 11:06:37 +1000 Subject: fsi/fsi-master-gpio: Sample input data on different clock phase We currently sample the input data right after we toggle the clock low, then high. The slave establishes the data on the rising edge, so this is not ideal. We should sample it on the low phase instead. This currently works because we have an extra delay, but subsequent patches will remove it. Signed-off-by: Benjamin Herrenschmidt Reviewed-by: Christopher Bostic Tested-by: Joel Stanley --- drivers/fsi/fsi-master-gpio.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/fsi/fsi-master-gpio.c b/drivers/fsi/fsi-master-gpio.c index 4295a46780cb..d6508bbad1fb 100644 --- a/drivers/fsi/fsi-master-gpio.c +++ b/drivers/fsi/fsi-master-gpio.c @@ -86,12 +86,15 @@ static void clock_toggle(struct fsi_master_gpio *master, int count) } } -static int sda_in(struct fsi_master_gpio *master) +static int sda_clock_in(struct fsi_master_gpio *master) { int in; ndelay(FSI_GPIO_STD_DLY); + gpiod_set_value(master->gpio_clk, 0); in = gpiod_get_value(master->gpio_data); + ndelay(FSI_GPIO_STD_DLY); + gpiod_set_value(master->gpio_clk, 1); return in ? 1 : 0; } @@ -126,8 +129,7 @@ static void serial_in(struct fsi_master_gpio *master, struct fsi_gpio_msg *msg, set_sda_input(master); for (bit = 0; bit < num_bits; bit++) { - clock_toggle(master, 1); - in_bit = sda_in(master); + in_bit = sda_clock_in(master); msg->msg <<= 1; msg->msg |= ~in_bit & 0x1; /* Data is active low */ } -- cgit v1.2.3 From bc1099d2b2519fa0f6c7613d8de398df8438a36b Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Tue, 8 May 2018 11:06:38 +1000 Subject: fsi/fsi-master-gpio: Add "no-gpio-delays" option This adds support for an optional device-tree property that makes the driver skip all the delays around clocking the GPIOs and set it in the device-tree of common POWER9 based OpenPower platforms. This useful on chips like the AST2500 where the GPIO block is running at a fairly low clock frequency (25Mhz typically). In this case, the delays are unnecessary and due to the low precision of the timers, actually quite harmful in terms of performance. Signed-off-by: Benjamin Herrenschmidt Reviewed-by: Christopher Bostic Tested-by: Joel Stanley --- arch/arm/boot/dts/aspeed-bmc-opp-romulus.dts | 1 + arch/arm/boot/dts/aspeed-bmc-opp-witherspoon.dts | 1 + arch/arm/boot/dts/aspeed-bmc-opp-zaius.dts | 1 + drivers/fsi/fsi-master-gpio.c | 20 ++++++++++++++++---- 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/arch/arm/boot/dts/aspeed-bmc-opp-romulus.dts b/arch/arm/boot/dts/aspeed-bmc-opp-romulus.dts index 389f5f83bef9..0b9b37d4d6ef 100644 --- a/arch/arm/boot/dts/aspeed-bmc-opp-romulus.dts +++ b/arch/arm/boot/dts/aspeed-bmc-opp-romulus.dts @@ -52,6 +52,7 @@ compatible = "fsi-master-gpio", "fsi-master"; #address-cells = <2>; #size-cells = <0>; + no-gpio-delays; clock-gpios = <&gpio ASPEED_GPIO(AA, 0) GPIO_ACTIVE_HIGH>; data-gpios = <&gpio ASPEED_GPIO(AA, 2) GPIO_ACTIVE_HIGH>; diff --git a/arch/arm/boot/dts/aspeed-bmc-opp-witherspoon.dts b/arch/arm/boot/dts/aspeed-bmc-opp-witherspoon.dts index 78a511e6e482..656036106001 100644 --- a/arch/arm/boot/dts/aspeed-bmc-opp-witherspoon.dts +++ b/arch/arm/boot/dts/aspeed-bmc-opp-witherspoon.dts @@ -153,6 +153,7 @@ compatible = "fsi-master-gpio", "fsi-master"; #address-cells = <2>; #size-cells = <0>; + no-gpio-delays; clock-gpios = <&gpio ASPEED_GPIO(AA, 0) GPIO_ACTIVE_HIGH>; data-gpios = <&gpio ASPEED_GPIO(E, 0) GPIO_ACTIVE_HIGH>; diff --git a/arch/arm/boot/dts/aspeed-bmc-opp-zaius.dts b/arch/arm/boot/dts/aspeed-bmc-opp-zaius.dts index ccbf645ab84d..2c5aa90a546d 100644 --- a/arch/arm/boot/dts/aspeed-bmc-opp-zaius.dts +++ b/arch/arm/boot/dts/aspeed-bmc-opp-zaius.dts @@ -91,6 +91,7 @@ compatible = "fsi-master-gpio", "fsi-master"; #address-cells = <2>; #size-cells = <0>; + no-gpio-delays; trans-gpios = <&gpio ASPEED_GPIO(O, 6) GPIO_ACTIVE_HIGH>; enable-gpios = <&gpio ASPEED_GPIO(D, 0) GPIO_ACTIVE_HIGH>; diff --git a/drivers/fsi/fsi-master-gpio.c b/drivers/fsi/fsi-master-gpio.c index d6508bbad1fb..c82bbd35276e 100644 --- a/drivers/fsi/fsi-master-gpio.c +++ b/drivers/fsi/fsi-master-gpio.c @@ -62,6 +62,7 @@ struct fsi_master_gpio { struct gpio_desc *gpio_enable; /* FSI enable */ struct gpio_desc *gpio_mux; /* Mux control */ bool external_mode; + bool no_delays; }; #define CREATE_TRACE_POINTS @@ -79,9 +80,11 @@ static void clock_toggle(struct fsi_master_gpio *master, int count) int i; for (i = 0; i < count; i++) { - ndelay(FSI_GPIO_STD_DLY); + if (!master->no_delays) + ndelay(FSI_GPIO_STD_DLY); gpiod_set_value(master->gpio_clk, 0); - ndelay(FSI_GPIO_STD_DLY); + if (!master->no_delays) + ndelay(FSI_GPIO_STD_DLY); gpiod_set_value(master->gpio_clk, 1); } } @@ -90,10 +93,12 @@ static int sda_clock_in(struct fsi_master_gpio *master) { int in; - ndelay(FSI_GPIO_STD_DLY); + if (!master->no_delays) + ndelay(FSI_GPIO_STD_DLY); gpiod_set_value(master->gpio_clk, 0); in = gpiod_get_value(master->gpio_data); - ndelay(FSI_GPIO_STD_DLY); + if (!master->no_delays) + ndelay(FSI_GPIO_STD_DLY); gpiod_set_value(master->gpio_clk, 1); return in ? 1 : 0; } @@ -677,6 +682,13 @@ static int fsi_master_gpio_probe(struct platform_device *pdev) } master->gpio_mux = gpio; + /* + * Check if GPIO block is slow enought that no extra delays + * are necessary. This improves performance on ast2500 by + * an order of magnitude. + */ + master->no_delays = device_property_present(&pdev->dev, "no-gpio-delays"); + master->master.n_links = 1; master->master.flags = FSI_MASTER_FLAG_SWCLOCK; master->master.read = fsi_master_gpio_read; -- cgit v1.2.3 From 97a4e5c95bf762ac254209dc4808ee5138ee574e Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Tue, 8 May 2018 11:06:39 +1000 Subject: fsi/fsi-master-gpio: Reduce turnaround clocks FSI_GPIO_PRIME_SLAVE_CLOCKS is the number of clocks if the "idle" phase between the end of a response and the beginning of the next one. It corresponds to tSendDelay in the FSI specification. The default value in the slave is 16 clocks. 100 is way overkill and significantly reduces the driver performance. This changes it to 20 (which gives the HW a bit of margin still just in case). Signed-off-by: Benjamin Herrenschmidt Reviewed-by: Christopher Bostic Tested-by: Joel Stanley --- drivers/fsi/fsi-master-gpio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/fsi/fsi-master-gpio.c b/drivers/fsi/fsi-master-gpio.c index c82bbd35276e..029b0a5b6d89 100644 --- a/drivers/fsi/fsi-master-gpio.c +++ b/drivers/fsi/fsi-master-gpio.c @@ -49,7 +49,7 @@ #define FSI_GPIO_CRC_SIZE 4 #define FSI_GPIO_MSG_ID_SIZE 2 #define FSI_GPIO_MSG_RESPID_SIZE 2 -#define FSI_GPIO_PRIME_SLAVE_CLOCKS 100 +#define FSI_GPIO_PRIME_SLAVE_CLOCKS 20 struct fsi_master_gpio { struct fsi_master master; -- cgit v1.2.3 From b632642e4ce22f1fbf2094752e936565771b34f4 Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Tue, 8 May 2018 11:46:18 +1000 Subject: fsi/fsi-master-gpio: Reduce dpoll clocks FSI_GPIO_DPOLL_CLOCKS is the number of clocks before sending a DPOLL command after receiving a BUSY status. It should be at least tSendDelay (16 clocks). According to comments in the code, it needs to also be at least 21 clocks due to HW issues. It's currently 100 clocks which impacts performances negatively in some cases. Reduces it in half to 50 clocks which seems to still be solid. Signed-off-by: Benjamin Herrenschmidt Reviewed-by: Christopher Bostic Tested-by: Joel Stanley --- drivers/fsi/fsi-master-gpio.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/fsi/fsi-master-gpio.c b/drivers/fsi/fsi-master-gpio.c index 029b0a5b6d89..bd2b2cbd5eb5 100644 --- a/drivers/fsi/fsi-master-gpio.c +++ b/drivers/fsi/fsi-master-gpio.c @@ -29,7 +29,8 @@ #define FSI_GPIO_CMD_TERM 0x3f #define FSI_GPIO_CMD_ABS_AR 0x4 -#define FSI_GPIO_DPOLL_CLOCKS 100 /* < 21 will cause slave to hang */ + +#define FSI_GPIO_DPOLL_CLOCKS 50 /* < 21 will cause slave to hang */ /* Bus errors */ #define FSI_GPIO_ERR_BUSY 1 /* Slave stuck in busy state */ @@ -43,7 +44,7 @@ #define FSI_GPIO_RESP_ACK 0 #define FSI_GPIO_RESP_ACKD 4 -#define FSI_GPIO_MAX_BUSY 100 +#define FSI_GPIO_MAX_BUSY 200 #define FSI_GPIO_MTOE_COUNT 1000 #define FSI_GPIO_DRAIN_BITS 20 #define FSI_GPIO_CRC_SIZE 4 -- cgit v1.2.3 From f3ca4834ac335bbf693d424326e4c93523a0c245 Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Tue, 15 May 2018 23:05:58 +1000 Subject: fsi/fsi-master-gpio: Delay sampling of FSI data input Most SoC GPIO implementations, including the Aspeed one, have synchronizers on the GPIO inputs. This means that the value read from a GPIO is a couple of clocks old, from whatever clock source feeds those synchronizers. In practice, this means that in no-delay mode, we are using a value that can potentially be a bit too old and too close to the clock edge establishing the data on the other side of the link. The voltage converters we use on some systems make this worse and sensitive to things like voltage fluctuations etc... This is, we believe, the cause of occasional CRC errors encountered during heavy activity on the LPC bus. This is fixed by introducing a dummy GPIO read before the actual data read. It slows down SBEFIFO by about 15% (less than any delay primitive) and the end result is so far solid. Signed-off-by: Benjamin Herrenschmidt Reviewed-by: Christopher Bostic Tested-by: Joel Stanley --- drivers/fsi/fsi-master-gpio.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/fsi/fsi-master-gpio.c b/drivers/fsi/fsi-master-gpio.c index bd2b2cbd5eb5..e1bde9e3f855 100644 --- a/drivers/fsi/fsi-master-gpio.c +++ b/drivers/fsi/fsi-master-gpio.c @@ -97,6 +97,11 @@ static int sda_clock_in(struct fsi_master_gpio *master) if (!master->no_delays) ndelay(FSI_GPIO_STD_DLY); gpiod_set_value(master->gpio_clk, 0); + + /* Dummy read to feed the synchronizers */ + gpiod_get_value(master->gpio_data); + + /* Actual data read */ in = gpiod_get_value(master->gpio_data); if (!master->no_delays) ndelay(FSI_GPIO_STD_DLY); -- cgit v1.2.3 From 8193fb4451ceb8c6585a529556542bdeb375e0d9 Mon Sep 17 00:00:00 2001 From: Jeremy Kerr Date: Thu, 10 May 2018 17:22:04 +0800 Subject: fsi/gpio: Include command build in locked section For implementing relative addressing mode, we'll need to build a command that is coherent with CFAM state. To do that, include the build_command_* functions in the locked section of read/write/term. Signed-off-by: Jeremy Kerr Signed-off-by: Benjamin Herrenschmidt Tested-by: Joel Stanley --- drivers/fsi/fsi-master-gpio.c | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/drivers/fsi/fsi-master-gpio.c b/drivers/fsi/fsi-master-gpio.c index e1bde9e3f855..d50ea4849377 100644 --- a/drivers/fsi/fsi-master-gpio.c +++ b/drivers/fsi/fsi-master-gpio.c @@ -463,14 +463,10 @@ static int fsi_master_gpio_xfer(struct fsi_master_gpio *master, uint8_t slave, { int rc; - mutex_lock(&master->cmd_lock); - rc = send_request(master, cmd); if (!rc) rc = poll_for_response(master, slave, resp_len, resp); - mutex_unlock(&master->cmd_lock); - return rc; } @@ -479,12 +475,17 @@ static int fsi_master_gpio_read(struct fsi_master *_master, int link, { struct fsi_master_gpio *master = to_fsi_master_gpio(_master); struct fsi_gpio_msg cmd; + int rc; if (link != 0) return -ENODEV; + mutex_lock(&master->cmd_lock); build_abs_ar_command(&cmd, id, addr, size, NULL); - return fsi_master_gpio_xfer(master, id, &cmd, size, val); + rc = fsi_master_gpio_xfer(master, id, &cmd, size, val); + mutex_unlock(&master->cmd_lock); + + return rc; } static int fsi_master_gpio_write(struct fsi_master *_master, int link, @@ -492,12 +493,17 @@ static int fsi_master_gpio_write(struct fsi_master *_master, int link, { struct fsi_master_gpio *master = to_fsi_master_gpio(_master); struct fsi_gpio_msg cmd; + int rc; if (link != 0) return -ENODEV; + mutex_lock(&master->cmd_lock); build_abs_ar_command(&cmd, id, addr, size, val); - return fsi_master_gpio_xfer(master, id, &cmd, 0, NULL); + rc = fsi_master_gpio_xfer(master, id, &cmd, 0, NULL); + mutex_unlock(&master->cmd_lock); + + return rc; } static int fsi_master_gpio_term(struct fsi_master *_master, @@ -505,12 +511,17 @@ static int fsi_master_gpio_term(struct fsi_master *_master, { struct fsi_master_gpio *master = to_fsi_master_gpio(_master); struct fsi_gpio_msg cmd; + int rc; if (link != 0) return -ENODEV; + mutex_lock(&master->cmd_lock); build_term_command(&cmd, id); - return fsi_master_gpio_xfer(master, id, &cmd, 0, NULL); + rc = fsi_master_gpio_xfer(master, id, &cmd, 0, NULL); + mutex_unlock(&master->cmd_lock); + + return rc; } static int fsi_master_gpio_break(struct fsi_master *_master, int link) -- cgit v1.2.3 From 0e82e5c1fad79ffe9d316296c7a4c1de539d0c48 Mon Sep 17 00:00:00 2001 From: Jeremy Kerr Date: Thu, 10 May 2018 17:22:05 +0800 Subject: fsi/gpio: Use relative-addressing commands FSI CFAMs support shorter commands that use a relative (or same) address as the last. This change introduces a last_addr to the master state, and uses it for subsequent reads/writes, and performs relative addressing when a subsequent read/write is in range. Signed-off-by: Jeremy Kerr Signed-off-by: Benjamin Herrenschmidt Reviewed-by: Christopher Bostic Tested-by: Joel Stanley --- drivers/fsi/fsi-master-gpio.c | 102 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 91 insertions(+), 11 deletions(-) diff --git a/drivers/fsi/fsi-master-gpio.c b/drivers/fsi/fsi-master-gpio.c index d50ea4849377..0a6799bda294 100644 --- a/drivers/fsi/fsi-master-gpio.c +++ b/drivers/fsi/fsi-master-gpio.c @@ -28,6 +28,8 @@ #define FSI_GPIO_CMD_DPOLL 0x2 #define FSI_GPIO_CMD_TERM 0x3f #define FSI_GPIO_CMD_ABS_AR 0x4 +#define FSI_GPIO_CMD_REL_AR 0x5 +#define FSI_GPIO_CMD_SAME_AR 0x3 /* but only a 2-bit opcode... */ #define FSI_GPIO_DPOLL_CLOCKS 50 /* < 21 will cause slave to hang */ @@ -52,6 +54,8 @@ #define FSI_GPIO_MSG_RESPID_SIZE 2 #define FSI_GPIO_PRIME_SLAVE_CLOCKS 20 +#define LAST_ADDR_INVALID 0x1 + struct fsi_master_gpio { struct fsi_master master; struct device *dev; @@ -64,6 +68,7 @@ struct fsi_master_gpio { struct gpio_desc *gpio_mux; /* Mux control */ bool external_mode; bool no_delays; + uint32_t last_addr; }; #define CREATE_TRACE_POINTS @@ -205,22 +210,89 @@ static void msg_push_crc(struct fsi_gpio_msg *msg) msg_push_bits(msg, crc, 4); } +static bool check_same_address(struct fsi_master_gpio *master, int id, + uint32_t addr) +{ + /* this will also handle LAST_ADDR_INVALID */ + return master->last_addr == (((id & 0x3) << 21) | (addr & ~0x3)); +} + +static bool check_relative_address(struct fsi_master_gpio *master, int id, + uint32_t addr, uint32_t *rel_addrp) +{ + uint32_t last_addr = master->last_addr; + int32_t rel_addr; + + if (last_addr == LAST_ADDR_INVALID) + return false; + + /* We may be in 23-bit addressing mode, which uses the id as the + * top two address bits. So, if we're referencing a different ID, + * use absolute addresses. + */ + if (((last_addr >> 21) & 0x3) != id) + return false; + + /* remove the top two bits from any 23-bit addressing */ + last_addr &= (1 << 21) - 1; + + /* We know that the addresses are limited to 21 bits, so this won't + * overflow the signed rel_addr */ + rel_addr = addr - last_addr; + if (rel_addr > 255 || rel_addr < -256) + return false; + + *rel_addrp = (uint32_t)rel_addr; + + return true; +} + +static void last_address_update(struct fsi_master_gpio *master, + int id, bool valid, uint32_t addr) +{ + if (!valid) + master->last_addr = LAST_ADDR_INVALID; + else + master->last_addr = ((id & 0x3) << 21) | (addr & ~0x3); +} + /* - * Encode an Absolute Address command + * Encode an Absolute/Relative/Same Address command */ -static void build_abs_ar_command(struct fsi_gpio_msg *cmd, - uint8_t id, uint32_t addr, size_t size, const void *data) +static void build_ar_command(struct fsi_master_gpio *master, + struct fsi_gpio_msg *cmd, uint8_t id, + uint32_t addr, size_t size, const void *data) { + int i, addr_bits, opcode_bits; bool write = !!data; - uint8_t ds; - int i; + uint8_t ds, opcode; + uint32_t rel_addr; cmd->bits = 0; cmd->msg = 0; - msg_push_bits(cmd, id, 2); - msg_push_bits(cmd, FSI_GPIO_CMD_ABS_AR, 3); - msg_push_bits(cmd, write ? 0 : 1, 1); + /* we have 21 bits of address max */ + addr &= ((1 << 21) - 1); + + /* cmd opcodes are variable length - SAME_AR is only two bits */ + opcode_bits = 3; + + if (check_same_address(master, id, addr)) { + /* we still address the byte offset within the word */ + addr_bits = 2; + opcode_bits = 2; + opcode = FSI_GPIO_CMD_SAME_AR; + + } else if (check_relative_address(master, id, addr, &rel_addr)) { + /* 8 bits plus sign */ + addr_bits = 9; + addr = rel_addr; + opcode = FSI_GPIO_CMD_REL_AR; + + } else { + addr_bits = 21; + opcode = FSI_GPIO_CMD_ABS_AR; + } /* * The read/write size is encoded in the lower bits of the address @@ -237,7 +309,10 @@ static void build_abs_ar_command(struct fsi_gpio_msg *cmd, if (size == 4) addr |= 1; - msg_push_bits(cmd, addr & ((1 << 21) - 1), 21); + msg_push_bits(cmd, id, 2); + msg_push_bits(cmd, opcode, opcode_bits); + msg_push_bits(cmd, write ? 0 : 1, 1); + msg_push_bits(cmd, addr, addr_bits); msg_push_bits(cmd, ds, 1); for (i = 0; write && i < size; i++) msg_push_bits(cmd, ((uint8_t *)data)[i], 8); @@ -481,8 +556,9 @@ static int fsi_master_gpio_read(struct fsi_master *_master, int link, return -ENODEV; mutex_lock(&master->cmd_lock); - build_abs_ar_command(&cmd, id, addr, size, NULL); + build_ar_command(master, &cmd, id, addr, size, NULL); rc = fsi_master_gpio_xfer(master, id, &cmd, size, val); + last_address_update(master, id, rc == 0, addr); mutex_unlock(&master->cmd_lock); return rc; @@ -499,8 +575,9 @@ static int fsi_master_gpio_write(struct fsi_master *_master, int link, return -ENODEV; mutex_lock(&master->cmd_lock); - build_abs_ar_command(&cmd, id, addr, size, val); + build_ar_command(master, &cmd, id, addr, size, val); rc = fsi_master_gpio_xfer(master, id, &cmd, 0, NULL); + last_address_update(master, id, rc == 0, addr); mutex_unlock(&master->cmd_lock); return rc; @@ -519,6 +596,7 @@ static int fsi_master_gpio_term(struct fsi_master *_master, mutex_lock(&master->cmd_lock); build_term_command(&cmd, id); rc = fsi_master_gpio_xfer(master, id, &cmd, 0, NULL); + last_address_update(master, id, false, 0); mutex_unlock(&master->cmd_lock); return rc; @@ -552,6 +630,7 @@ static int fsi_master_gpio_break(struct fsi_master *_master, int link) clock_toggle(master, FSI_POST_BREAK_CLOCKS); spin_unlock_irqrestore(&master->bit_lock, flags); + last_address_update(master, 0, false, 0); mutex_unlock(&master->cmd_lock); /* Wait for logic reset to take effect */ @@ -662,6 +741,7 @@ static int fsi_master_gpio_probe(struct platform_device *pdev) master->dev = &pdev->dev; master->master.dev.parent = master->dev; master->master.dev.of_node = of_node_get(dev_of_node(master->dev)); + master->last_addr = LAST_ADDR_INVALID; gpio = devm_gpiod_get(&pdev->dev, "clock", 0); if (IS_ERR(gpio)) { -- cgit v1.2.3 From 4e56828a5db19e2de8f8dc464c6df2e7e9ff4e13 Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Tue, 15 May 2018 16:14:43 +1000 Subject: fsi/fsi-master-gpio: Implement CRC error recovery The FSI protocol defines two modes of recovery from CRC errors, this implements both: - If the device returns an ECRC (it detected a CRC error in the command), then we simply issue the command again. - If the master detects a CRC error in the response, we send an E_POLL command which requests a resend of the response without actually re-executing the command (which could otherwise have unwanted side effects such as dequeuing a FIFO twice). Signed-off-by: Benjamin Herrenschmidt Reviewed-by: Christopher Bostic Tested-by: Joel Stanley --- Note: This was actually tested by removing some of my fixes, thus causing us to hit occasional CRC errors during high LPC activity. --- drivers/fsi/fsi-master-gpio.c | 90 +++++++++++++++++++++++++++------- include/trace/events/fsi_master_gpio.h | 27 ++++++++++ 2 files changed, 99 insertions(+), 18 deletions(-) diff --git a/drivers/fsi/fsi-master-gpio.c b/drivers/fsi/fsi-master-gpio.c index 0a6799bda294..351c12f2ac55 100644 --- a/drivers/fsi/fsi-master-gpio.c +++ b/drivers/fsi/fsi-master-gpio.c @@ -22,20 +22,23 @@ #define FSI_BREAK_CLOCKS 256 /* Number of clocks to issue break */ #define FSI_POST_BREAK_CLOCKS 16000 /* Number clocks to set up cfam */ #define FSI_INIT_CLOCKS 5000 /* Clock out any old data */ +#define FSI_GPIO_DPOLL_CLOCKS 50 /* < 21 will cause slave to hang */ +#define FSI_GPIO_EPOLL_CLOCKS 50 /* Number of clocks for E_POLL retry */ #define FSI_GPIO_STD_DELAY 10 /* Standard GPIO delay in nS */ /* todo: adjust down as low as */ /* possible or eliminate */ +#define FSI_CRC_ERR_RETRIES 10 + #define FSI_GPIO_CMD_DPOLL 0x2 +#define FSI_GPIO_CMD_EPOLL 0x3 #define FSI_GPIO_CMD_TERM 0x3f #define FSI_GPIO_CMD_ABS_AR 0x4 #define FSI_GPIO_CMD_REL_AR 0x5 #define FSI_GPIO_CMD_SAME_AR 0x3 /* but only a 2-bit opcode... */ - -#define FSI_GPIO_DPOLL_CLOCKS 50 /* < 21 will cause slave to hang */ - -/* Bus errors */ -#define FSI_GPIO_ERR_BUSY 1 /* Slave stuck in busy state */ +/* Slave responses */ +#define FSI_GPIO_RESP_ACK 0 /* Success */ +#define FSI_GPIO_RESP_BUSY 1 /* Slave busy */ #define FSI_GPIO_RESP_ERRA 2 /* Any (misc) Error */ #define FSI_GPIO_RESP_ERRC 3 /* Slave reports master CRC error */ #define FSI_GPIO_MTOE 4 /* Master time out error */ @@ -330,6 +333,16 @@ static void build_dpoll_command(struct fsi_gpio_msg *cmd, uint8_t slave_id) msg_push_crc(cmd); } +static void build_epoll_command(struct fsi_gpio_msg *cmd, uint8_t slave_id) +{ + cmd->bits = 0; + cmd->msg = 0; + + msg_push_bits(cmd, slave_id, 2); + msg_push_bits(cmd, FSI_GPIO_CMD_EPOLL, 3); + msg_push_crc(cmd); +} + static void echo_delay(struct fsi_master_gpio *master) { set_sda_output(master, 1); @@ -355,6 +368,12 @@ static void fsi_master_gpio_error(struct fsi_master_gpio *master, int error) } +/* + * Note: callers rely specifically on this returning -EAGAIN for + * a CRC error detected in the response. Use other error code + * for other situations. It will be converted to something else + * higher up the stack before it reaches userspace. + */ static int read_one_response(struct fsi_master_gpio *master, uint8_t data_size, struct fsi_gpio_msg *msgp, uint8_t *tagp) { @@ -379,7 +398,7 @@ static int read_one_response(struct fsi_master_gpio *master, "Master time out waiting for response\n"); fsi_master_gpio_error(master, FSI_GPIO_MTOE); spin_unlock_irqrestore(&master->bit_lock, flags); - return -EIO; + return -ETIMEDOUT; } msg.bits = 0; @@ -405,7 +424,7 @@ static int read_one_response(struct fsi_master_gpio *master, if (crc) { dev_dbg(master->dev, "ERR response CRC\n"); fsi_master_gpio_error(master, FSI_GPIO_CRC_INVAL); - return -EIO; + return -EAGAIN; } if (msgp) @@ -451,11 +470,33 @@ static int poll_for_response(struct fsi_master_gpio *master, unsigned long flags; uint8_t tag; uint8_t *data_byte = data; - + int crc_err_retries = 0; retry: rc = read_one_response(master, size, &response, &tag); - if (rc) - return rc; + + /* Handle retries on CRC errors */ + if (rc == -EAGAIN) { + /* Too many retries ? */ + if (crc_err_retries++ > FSI_CRC_ERR_RETRIES) { + /* + * Pass it up as a -EIO otherwise upper level will retry + * the whole command which isn't what we want here. + */ + rc = -EIO; + goto fail; + } + dev_dbg(master->dev, + "CRC error retry %d\n", crc_err_retries); + trace_fsi_master_gpio_crc_rsp_error(master); + build_epoll_command(&cmd, slave); + spin_lock_irqsave(&master->bit_lock, flags); + clock_zeros(master, FSI_GPIO_EPOLL_CLOCKS); + serial_out(master, &cmd); + echo_delay(master); + spin_unlock_irqrestore(&master->bit_lock, flags); + goto retry; + } else if (rc) + goto fail; switch (tag) { case FSI_GPIO_RESP_ACK: @@ -496,18 +537,21 @@ retry: break; case FSI_GPIO_RESP_ERRA: - case FSI_GPIO_RESP_ERRC: - dev_dbg(master->dev, "ERR%c received: 0x%x\n", - tag == FSI_GPIO_RESP_ERRA ? 'A' : 'C', - (int)response.msg); + dev_dbg(master->dev, "ERRA received: 0x%x\n", (int)response.msg); fsi_master_gpio_error(master, response.msg); rc = -EIO; break; + case FSI_GPIO_RESP_ERRC: + dev_dbg(master->dev, "ERRC received: 0x%x\n", (int)response.msg); + fsi_master_gpio_error(master, response.msg); + trace_fsi_master_gpio_crc_cmd_error(master); + rc = -EAGAIN; + break; } if (busy_count > 0) trace_fsi_master_gpio_poll_response_busy(master, busy_count); - + fail: /* Clock the slave enough to be ready for next operation */ spin_lock_irqsave(&master->bit_lock, flags); clock_zeros(master, FSI_GPIO_PRIME_SLAVE_CLOCKS); @@ -536,11 +580,21 @@ static int send_request(struct fsi_master_gpio *master, static int fsi_master_gpio_xfer(struct fsi_master_gpio *master, uint8_t slave, struct fsi_gpio_msg *cmd, size_t resp_len, void *resp) { - int rc; + int rc = -EAGAIN, retries = 0; - rc = send_request(master, cmd); - if (!rc) + while ((retries++) < FSI_CRC_ERR_RETRIES) { + rc = send_request(master, cmd); + if (rc) + break; rc = poll_for_response(master, slave, resp_len, resp); + if (rc != -EAGAIN) + break; + rc = -EIO; + dev_warn(master->dev, "ECRC retry %d\n", retries); + + /* Pace it a bit before retry */ + msleep(1); + } return rc; } diff --git a/include/trace/events/fsi_master_gpio.h b/include/trace/events/fsi_master_gpio.h index 33e928c5acf3..389082132433 100644 --- a/include/trace/events/fsi_master_gpio.h +++ b/include/trace/events/fsi_master_gpio.h @@ -64,6 +64,33 @@ TRACE_EVENT(fsi_master_gpio_break, ) ); +TRACE_EVENT(fsi_master_gpio_crc_cmd_error, + TP_PROTO(const struct fsi_master_gpio *master), + TP_ARGS(master), + TP_STRUCT__entry( + __field(int, master_idx) + ), + TP_fast_assign( + __entry->master_idx = master->master.idx; + ), + TP_printk("fsi-gpio%d ----CRC command retry---", + __entry->master_idx + ) +); + +TRACE_EVENT(fsi_master_gpio_crc_rsp_error, + TP_PROTO(const struct fsi_master_gpio *master), + TP_ARGS(master), + TP_STRUCT__entry( + __field(int, master_idx) + ), + TP_fast_assign( + __entry->master_idx = master->master.idx; + ), + TP_printk("fsi-gpio%d ----CRC response---", + __entry->master_idx + ) +); TRACE_EVENT(fsi_master_gpio_poll_response_busy, TP_PROTO(const struct fsi_master_gpio *master, int busy), -- cgit v1.2.3 From c49e34401a32acc05bafec2b3d28d8e58410724c Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Tue, 15 May 2018 13:40:21 +1000 Subject: fsi/fsi-master-gpio: More error handling cleanup Remove calls to the empty and useless fsi_master_gpio_error() function, and report CRC errors as "FSI_ERR_NO_SLAVE" when reading an all 1's response. Signed-off-by: Benjamin Herrenschmidt Tested-by: Joel Stanley --- drivers/fsi/fsi-master-gpio.c | 26 +++++--------------------- 1 file changed, 5 insertions(+), 21 deletions(-) diff --git a/drivers/fsi/fsi-master-gpio.c b/drivers/fsi/fsi-master-gpio.c index 351c12f2ac55..da556da62846 100644 --- a/drivers/fsi/fsi-master-gpio.c +++ b/drivers/fsi/fsi-master-gpio.c @@ -41,13 +41,6 @@ #define FSI_GPIO_RESP_BUSY 1 /* Slave busy */ #define FSI_GPIO_RESP_ERRA 2 /* Any (misc) Error */ #define FSI_GPIO_RESP_ERRC 3 /* Slave reports master CRC error */ -#define FSI_GPIO_MTOE 4 /* Master time out error */ -#define FSI_GPIO_CRC_INVAL 5 /* Master reports slave CRC error */ - -/* Normal slave responses */ -#define FSI_GPIO_RESP_BUSY 1 -#define FSI_GPIO_RESP_ACK 0 -#define FSI_GPIO_RESP_ACKD 4 #define FSI_GPIO_MAX_BUSY 200 #define FSI_GPIO_MTOE_COUNT 1000 @@ -359,15 +352,6 @@ static void build_term_command(struct fsi_gpio_msg *cmd, uint8_t slave_id) msg_push_crc(cmd); } -/* - * Store information on master errors so handler can detect and clean - * up the bus - */ -static void fsi_master_gpio_error(struct fsi_master_gpio *master, int error) -{ - -} - /* * Note: callers rely specifically on this returning -EAGAIN for * a CRC error detected in the response. Use other error code @@ -396,7 +380,6 @@ static int read_one_response(struct fsi_master_gpio *master, if (i == FSI_GPIO_MTOE_COUNT) { dev_dbg(master->dev, "Master time out waiting for response\n"); - fsi_master_gpio_error(master, FSI_GPIO_MTOE); spin_unlock_irqrestore(&master->bit_lock, flags); return -ETIMEDOUT; } @@ -422,8 +405,11 @@ static int read_one_response(struct fsi_master_gpio *master, crc = crc4(0, 1, 1); crc = crc4(crc, msg.msg, msg.bits); if (crc) { - dev_dbg(master->dev, "ERR response CRC\n"); - fsi_master_gpio_error(master, FSI_GPIO_CRC_INVAL); + /* Check if it's all 1's, that probably means the host is off */ + if (((~msg.msg) & ((1ull << msg.bits) - 1)) == 0) + return -ENODEV; + dev_dbg(master->dev, "ERR response CRC msg: 0x%016llx (%d bits)\n", + msg.msg, msg.bits); return -EAGAIN; } @@ -538,12 +524,10 @@ retry: case FSI_GPIO_RESP_ERRA: dev_dbg(master->dev, "ERRA received: 0x%x\n", (int)response.msg); - fsi_master_gpio_error(master, response.msg); rc = -EIO; break; case FSI_GPIO_RESP_ERRC: dev_dbg(master->dev, "ERRC received: 0x%x\n", (int)response.msg); - fsi_master_gpio_error(master, response.msg); trace_fsi_master_gpio_crc_cmd_error(master); rc = -EAGAIN; break; -- cgit v1.2.3 From 26d79b272d388e2180d93d01b4085208c028f79a Mon Sep 17 00:00:00 2001 From: Jeremy Kerr Date: Thu, 24 May 2018 11:29:56 +0800 Subject: fsi/master-gpio: Replace bit_bit lock with IRQ disable/enable We currently use a spinlock (bit_lock) around operations that clock bits out of the FSI bus, and a mutex to protect against simultaneous access to the master. This means that bit_lock isn't needed for mutual exlusion, only to prevent timing issues when clocking bits out. To reflect this, this change converts bit_lock to just the local_irq_save/restore operation. Signed-off-by: Jeremy Kerr Signed-off-by: Benjamin Herrenschmidt Tested-by: Joel Stanley --- drivers/fsi/fsi-master-gpio.c | 48 +++++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/drivers/fsi/fsi-master-gpio.c b/drivers/fsi/fsi-master-gpio.c index da556da62846..084e9da8d151 100644 --- a/drivers/fsi/fsi-master-gpio.c +++ b/drivers/fsi/fsi-master-gpio.c @@ -8,11 +8,11 @@ #include #include #include +#include #include #include #include #include -#include #include "fsi-master.h" @@ -56,7 +56,6 @@ struct fsi_master_gpio { struct fsi_master master; struct device *dev; struct mutex cmd_lock; /* mutex for command ordering */ - spinlock_t bit_lock; /* lock for clocking bits out */ struct gpio_desc *gpio_clk; struct gpio_desc *gpio_data; struct gpio_desc *gpio_trans; /* Voltage translator */ @@ -367,7 +366,7 @@ static int read_one_response(struct fsi_master_gpio *master, uint8_t tag; int i; - spin_lock_irqsave(&master->bit_lock, flags); + local_irq_save(flags); /* wait for the start bit */ for (i = 0; i < FSI_GPIO_MTOE_COUNT; i++) { @@ -380,7 +379,7 @@ static int read_one_response(struct fsi_master_gpio *master, if (i == FSI_GPIO_MTOE_COUNT) { dev_dbg(master->dev, "Master time out waiting for response\n"); - spin_unlock_irqrestore(&master->bit_lock, flags); + local_irq_restore(flags); return -ETIMEDOUT; } @@ -399,7 +398,7 @@ static int read_one_response(struct fsi_master_gpio *master, /* read CRC */ serial_in(master, &msg, FSI_GPIO_CRC_SIZE); - spin_unlock_irqrestore(&master->bit_lock, flags); + local_irq_restore(flags); /* we have a whole message now; check CRC */ crc = crc4(0, 1, 1); @@ -430,10 +429,10 @@ static int issue_term(struct fsi_master_gpio *master, uint8_t slave) build_term_command(&cmd, slave); - spin_lock_irqsave(&master->bit_lock, flags); + local_irq_save(flags); serial_out(master, &cmd); echo_delay(master); - spin_unlock_irqrestore(&master->bit_lock, flags); + local_irq_restore(flags); rc = read_one_response(master, 0, NULL, &tag); if (rc < 0) { @@ -475,11 +474,11 @@ retry: "CRC error retry %d\n", crc_err_retries); trace_fsi_master_gpio_crc_rsp_error(master); build_epoll_command(&cmd, slave); - spin_lock_irqsave(&master->bit_lock, flags); + local_irq_save(flags); clock_zeros(master, FSI_GPIO_EPOLL_CLOCKS); serial_out(master, &cmd); echo_delay(master); - spin_unlock_irqrestore(&master->bit_lock, flags); + local_irq_restore(flags); goto retry; } else if (rc) goto fail; @@ -506,18 +505,18 @@ retry: */ if (busy_count++ < FSI_GPIO_MAX_BUSY) { build_dpoll_command(&cmd, slave); - spin_lock_irqsave(&master->bit_lock, flags); + local_irq_save(flags); clock_zeros(master, FSI_GPIO_DPOLL_CLOCKS); serial_out(master, &cmd); echo_delay(master); - spin_unlock_irqrestore(&master->bit_lock, flags); + local_irq_restore(flags); goto retry; } dev_warn(master->dev, "ERR slave is stuck in busy state, issuing TERM\n"); - spin_lock_irqsave(&master->bit_lock, flags); + local_irq_save(flags); clock_zeros(master, FSI_GPIO_DPOLL_CLOCKS); - spin_unlock_irqrestore(&master->bit_lock, flags); + local_irq_restore(flags); issue_term(master, slave); rc = -EIO; break; @@ -537,9 +536,10 @@ retry: trace_fsi_master_gpio_poll_response_busy(master, busy_count); fail: /* Clock the slave enough to be ready for next operation */ - spin_lock_irqsave(&master->bit_lock, flags); + local_irq_save(flags); clock_zeros(master, FSI_GPIO_PRIME_SLAVE_CLOCKS); - spin_unlock_irqrestore(&master->bit_lock, flags); + local_irq_restore(flags); + return rc; } @@ -548,15 +548,13 @@ static int send_request(struct fsi_master_gpio *master, { unsigned long flags; - spin_lock_irqsave(&master->bit_lock, flags); - if (master->external_mode) { - spin_unlock_irqrestore(&master->bit_lock, flags); + if (master->external_mode) return -EBUSY; - } + local_irq_save(flags); serial_out(master, cmd); echo_delay(master); - spin_unlock_irqrestore(&master->bit_lock, flags); + local_irq_restore(flags); return 0; } @@ -656,7 +654,7 @@ static int fsi_master_gpio_break(struct fsi_master *_master, int link) return -EBUSY; } - spin_lock_irqsave(&master->bit_lock, flags); + local_irq_save(flags); set_sda_output(master, 1); sda_out(master, 1); @@ -667,7 +665,8 @@ static int fsi_master_gpio_break(struct fsi_master *_master, int link) sda_out(master, 1); clock_toggle(master, FSI_POST_BREAK_CLOCKS); - spin_unlock_irqrestore(&master->bit_lock, flags); + local_irq_restore(flags); + last_address_update(master, 0, false, 0); mutex_unlock(&master->cmd_lock); @@ -688,9 +687,9 @@ static void fsi_master_gpio_init(struct fsi_master_gpio *master) gpiod_direction_output(master->gpio_data, 1); /* todo: evaluate if clocks can be reduced */ - spin_lock_irqsave(&master->bit_lock, flags); + local_irq_save(flags); clock_zeros(master, FSI_INIT_CLOCKS); - spin_unlock_irqrestore(&master->bit_lock, flags); + local_irq_restore(flags); } static void fsi_master_gpio_init_external(struct fsi_master_gpio *master) @@ -832,7 +831,6 @@ static int fsi_master_gpio_probe(struct platform_device *pdev) master->master.send_break = fsi_master_gpio_break; master->master.link_enable = fsi_master_gpio_link_enable; platform_set_drvdata(pdev, master); - spin_lock_init(&master->bit_lock); mutex_init(&master->cmd_lock); fsi_master_gpio_init(master); -- cgit v1.2.3 From 52b7116e885e95a0d4206cca7f11ef332bb1dd89 Mon Sep 17 00:00:00 2001 From: Eddie James Date: Tue, 8 May 2018 17:43:32 -0500 Subject: fsi: scom: Remove PIB reset during probe The PIB reset causes problems for the running P9 chip. The reset shouldn't be performed by this driver. Signed-off-by: Eddie James Reviewed-by: Christopher Bostic Signed-off-by: Benjamin Herrenschmidt Tested-by: Joel Stanley --- drivers/fsi/fsi-scom.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/drivers/fsi/fsi-scom.c b/drivers/fsi/fsi-scom.c index e13353a2fd7c..c8eb5e5b94a7 100644 --- a/drivers/fsi/fsi-scom.c +++ b/drivers/fsi/fsi-scom.c @@ -26,15 +26,11 @@ #define FSI_ENGID_SCOM 0x5 -#define SCOM_FSI2PIB_DELAY 50 - /* SCOM engine register set */ #define SCOM_DATA0_REG 0x00 #define SCOM_DATA1_REG 0x04 #define SCOM_CMD_REG 0x08 -#define SCOM_RESET_REG 0x1C -#define SCOM_RESET_CMD 0x80000000 #define SCOM_WRITE_CMD 0x80000000 struct scom_device { @@ -180,7 +176,6 @@ static const struct file_operations scom_fops = { static int scom_probe(struct device *dev) { - uint32_t data; struct fsi_device *fsi_dev = to_fsi_dev(dev); struct scom_device *scom; @@ -197,9 +192,6 @@ static int scom_probe(struct device *dev) scom->mdev.parent = dev; list_add(&scom->link, &scom_devices); - data = cpu_to_be32(SCOM_RESET_CMD); - fsi_device_write(fsi_dev, SCOM_RESET_REG, &data, sizeof(uint32_t)); - return misc_register(&scom->mdev); } -- cgit v1.2.3 From 9f4a8a2d7f9d71093f41c4bb0ef8707e8145bad3 Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Thu, 17 May 2018 15:58:00 +1000 Subject: fsi/sbefifo: Add driver for the SBE FIFO This driver provides an in-kernel and a user API for accessing the command FIFO of the SBE (Self Boot Engine) of the POWER9 processor, via the FSI bus. It provides an in-kernel interface to submit command and receive responses, along with a helper to locate and analyse the response status block. It's a simple synchronous submit() type API. The user interface uses the write/read interface that an earlier version of this driver already provided, however it has some specific limitations in order to keep the driver simple and avoid using up a lot of kernel memory: - The user should perform a single write() with the command and a single read() to get the response (with a buffer big enough to hold the entire response). - On a write() the command is simply "stored" into a kernel buffer, it is submitted as one operation on the subsequent read(). This allows to have the code write directly from the FIFO into the user buffer and avoid hogging the SBE between the write() and read() syscall as it's critical that the SBE be freed asap to respond to the host. An extra write() will simply replace the previously written command. - A write of a single 4 bytes containing the value 0x52534554 in big endian will trigger a reset request. No read is necessary, the write() call will return when the reset has been acknowledged or times out. - The command is limited to 4K bytes. Signed-off-by: Benjamin Herrenschmidt Tested-by: Joel Stanley --- --- drivers/fsi/Kconfig | 7 + drivers/fsi/Makefile | 1 + drivers/fsi/fsi-sbefifo.c | 1005 +++++++++++++++++++++++++++++++++++++++++++ include/linux/fsi-sbefifo.h | 33 ++ 4 files changed, 1046 insertions(+) create mode 100644 drivers/fsi/fsi-sbefifo.c create mode 100644 include/linux/fsi-sbefifo.h diff --git a/drivers/fsi/Kconfig b/drivers/fsi/Kconfig index a326ed663d3c..24f84a96b8b9 100644 --- a/drivers/fsi/Kconfig +++ b/drivers/fsi/Kconfig @@ -32,4 +32,11 @@ config FSI_SCOM ---help--- This option enables an FSI based SCOM device driver. +config FSI_SBEFIFO + tristate "SBEFIFO FSI client device driver" + ---help--- + This option enables an FSI based SBEFIFO device driver. The SBEFIFO is + a pipe-like FSI device for communicating with the self boot engine + (SBE) on POWER processors. + endif diff --git a/drivers/fsi/Makefile b/drivers/fsi/Makefile index 65eb99dfafdb..851182e1cd9e 100644 --- a/drivers/fsi/Makefile +++ b/drivers/fsi/Makefile @@ -3,3 +3,4 @@ obj-$(CONFIG_FSI) += fsi-core.o obj-$(CONFIG_FSI_MASTER_HUB) += fsi-master-hub.o obj-$(CONFIG_FSI_MASTER_GPIO) += fsi-master-gpio.o obj-$(CONFIG_FSI_SCOM) += fsi-scom.o +obj-$(CONFIG_FSI_SBEFIFO) += fsi-sbefifo.o diff --git a/drivers/fsi/fsi-sbefifo.c b/drivers/fsi/fsi-sbefifo.c new file mode 100644 index 000000000000..9b8b6b346af6 --- /dev/null +++ b/drivers/fsi/fsi-sbefifo.c @@ -0,0 +1,1005 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) IBM Corporation 2017 + * + * 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 + * MERGCHANTABILITY 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 + +/* + * The SBEFIFO is a pipe-like FSI device for communicating with + * the self boot engine on POWER processors. + */ + +#define DEVICE_NAME "sbefifo" +#define FSI_ENGID_SBE 0x22 + +/* + * Register layout + */ + +/* Register banks */ +#define SBEFIFO_UP 0x00 /* FSI -> Host */ +#define SBEFIFO_DOWN 0x40 /* Host -> FSI */ + +/* Per-bank registers */ +#define SBEFIFO_FIFO 0x00 /* The FIFO itself */ +#define SBEFIFO_STS 0x04 /* Status register */ +#define SBEFIFO_STS_PARITY_ERR 0x20000000 +#define SBEFIFO_STS_RESET_REQ 0x02000000 +#define SBEFIFO_STS_GOT_EOT 0x00800000 +#define SBEFIFO_STS_MAX_XFER_LIMIT 0x00400000 +#define SBEFIFO_STS_FULL 0x00200000 +#define SBEFIFO_STS_EMPTY 0x00100000 +#define SBEFIFO_STS_ECNT_MASK 0x000f0000 +#define SBEFIFO_STS_ECNT_SHIFT 16 +#define SBEFIFO_STS_VALID_MASK 0x0000ff00 +#define SBEFIFO_STS_VALID_SHIFT 8 +#define SBEFIFO_STS_EOT_MASK 0x000000ff +#define SBEFIFO_STS_EOT_SHIFT 0 +#define SBEFIFO_EOT_RAISE 0x08 /* (Up only) Set End Of Transfer */ +#define SBEFIFO_REQ_RESET 0x0C /* (Up only) Reset Request */ +#define SBEFIFO_PERFORM_RESET 0x10 /* (Down only) Perform Reset */ +#define SBEFIFO_EOT_ACK 0x14 /* (Down only) Acknowledge EOT */ +#define SBEFIFO_DOWN_MAX 0x18 /* (Down only) Max transfer */ + +/* CFAM GP Mailbox SelfBoot Message register */ +#define CFAM_GP_MBOX_SBM_ADDR 0x2824 /* Converted 0x2809 */ + +#define CFAM_SBM_SBE_BOOTED 0x80000000 +#define CFAM_SBM_SBE_ASYNC_FFDC 0x40000000 +#define CFAM_SBM_SBE_STATE_MASK 0x00f00000 +#define CFAM_SBM_SBE_STATE_SHIFT 20 + +enum sbe_state +{ + SBE_STATE_UNKNOWN = 0x0, // Unkown, initial state + SBE_STATE_IPLING = 0x1, // IPL'ing - autonomous mode (transient) + SBE_STATE_ISTEP = 0x2, // ISTEP - Running IPL by steps (transient) + SBE_STATE_MPIPL = 0x3, // MPIPL + SBE_STATE_RUNTIME = 0x4, // SBE Runtime + SBE_STATE_DMT = 0x5, // Dead Man Timer State (transient) + SBE_STATE_DUMP = 0x6, // Dumping + SBE_STATE_FAILURE = 0x7, // Internal SBE failure + SBE_STATE_QUIESCE = 0x8, // Final state - needs SBE reset to get out +}; + +/* FIFO depth */ +#define SBEFIFO_FIFO_DEPTH 8 + +/* Helpers */ +#define sbefifo_empty(sts) ((sts) & SBEFIFO_STS_EMPTY) +#define sbefifo_full(sts) ((sts) & SBEFIFO_STS_FULL) +#define sbefifo_parity_err(sts) ((sts) & SBEFIFO_STS_PARITY_ERR) +#define sbefifo_populated(sts) (((sts) & SBEFIFO_STS_ECNT_MASK) >> SBEFIFO_STS_ECNT_SHIFT) +#define sbefifo_vacant(sts) (SBEFIFO_FIFO_DEPTH - sbefifo_populated(sts)) +#define sbefifo_eot_set(sts) (((sts) & SBEFIFO_STS_EOT_MASK) >> SBEFIFO_STS_EOT_SHIFT) + +/* Reset request timeout in ms */ +#define SBEFIFO_RESET_TIMEOUT 10000 + +/* Timeouts for commands in ms */ +#define SBEFIFO_TIMEOUT_START_CMD 10000 +#define SBEFIFO_TIMEOUT_IN_CMD 1000 +#define SBEFIFO_TIMEOUT_START_RSP 10000 +#define SBEFIFO_TIMEOUT_IN_RSP 1000 + +/* Other constants */ +#define SBEFIFO_MAX_CMD_LEN PAGE_SIZE +#define SBEFIFO_RESET_MAGIC 0x52534554 /* "RSET" */ + +struct sbefifo { + uint32_t magic; +#define SBEFIFO_MAGIC 0x53424546 /* "SBEF" */ + struct fsi_device *fsi_dev; + struct miscdevice mdev; + struct mutex lock; + char name[32]; + int idx; + bool broken; + bool async_ffdc; +}; + +struct sbefifo_user { + struct sbefifo *sbefifo; + struct mutex file_lock; + void *pending_cmd; + size_t pending_len; +}; + +static DEFINE_IDA(sbefifo_ida); +static DEFINE_MUTEX(sbefifo_ffdc_mutex); + + +static void sbefifo_dump_ffdc(struct device *dev, const __be32 *ffdc, + size_t ffdc_sz, bool internal) +{ + int pack = 0; +#define FFDC_LSIZE 60 + static char ffdc_line[FFDC_LSIZE]; + char *p = ffdc_line; + + mutex_lock(&sbefifo_ffdc_mutex); + while (ffdc_sz) { + u32 w0, w1, w2, i; + if (ffdc_sz < 3) { + dev_err(dev, "SBE invalid FFDC package size %zd\n", ffdc_sz); + return; + } + w0 = be32_to_cpu(*(ffdc++)); + w1 = be32_to_cpu(*(ffdc++)); + w2 = be32_to_cpu(*(ffdc++)); + ffdc_sz -= 3; + if ((w0 >> 16) != 0xFFDC) { + dev_err(dev, "SBE invalid FFDC package signature %08x %08x %08x\n", + w0, w1, w2); + break; + } + w0 &= 0xffff; + if (w0 > ffdc_sz) { + dev_err(dev, "SBE FFDC package len %d words but only %zd remaining\n", + w0, ffdc_sz); + w0 = ffdc_sz; + break; + } + if (internal) { + dev_warn(dev, "+---- SBE FFDC package %d for async err -----+\n", + pack++); + } else { + dev_warn(dev, "+---- SBE FFDC package %d for cmd %02x:%02x -----+\n", + pack++, (w1 >> 8) & 0xff, w1 & 0xff); + } + dev_warn(dev, "| Response code: %08x |\n", w2); + dev_warn(dev, "|-------------------------------------------|\n"); + for (i = 0; i < w0; i++) { + if ((i & 3) == 0) { + p = ffdc_line; + p += sprintf(p, "| %04x:", i << 4); + } + p += sprintf(p, " %08x", be32_to_cpu(*(ffdc++))); + ffdc_sz--; + if ((i & 3) == 3 || i == (w0 - 1)) { + while ((i & 3) < 3) { + p += sprintf(p, " "); + i++; + } + dev_warn(dev, "%s |\n", ffdc_line); + } + } + dev_warn(dev, "+-------------------------------------------+\n"); + } +} + +int sbefifo_parse_status(struct device *dev, u16 cmd, __be32 *response, + size_t resp_len, size_t *data_len) +{ + u32 dh, s0, s1; + size_t ffdc_sz; + + if (resp_len < 3) { + pr_debug("sbefifo: cmd %04x, response too small: %zd\n", + cmd, resp_len); + return -ENXIO; + } + dh = be32_to_cpu(response[resp_len - 1]); + if (dh > resp_len || dh < 3) { + dev_err(dev, "SBE cmd %02x:%02x status offset out of range: %d/%zd\n", + cmd >> 8, cmd & 0xff, dh, resp_len); + return -ENXIO; + } + s0 = be32_to_cpu(response[resp_len - dh]); + s1 = be32_to_cpu(response[resp_len - dh + 1]); + if (((s0 >> 16) != 0xC0DE) || ((s0 & 0xffff) != cmd)) { + dev_err(dev, "SBE cmd %02x:%02x, status signature invalid: 0x%08x 0x%08x\n", + cmd >> 8, cmd & 0xff, s0, s1); + return -ENXIO; + } + if (s1 != 0) { + ffdc_sz = dh - 3; + dev_warn(dev, "SBE error cmd %02x:%02x status=%04x:%04x\n", + cmd >> 8, cmd & 0xff, s1 >> 16, s1 & 0xffff); + if (ffdc_sz) + sbefifo_dump_ffdc(dev, &response[resp_len - dh + 2], + ffdc_sz, false); + } + if (data_len) + *data_len = resp_len - dh; + + /* + * Primary status don't have the top bit set, so can't be confused with + * Linux negative error codes, so return the status word whole. + */ + return s1; +} +EXPORT_SYMBOL_GPL(sbefifo_parse_status); + +static int sbefifo_regr(struct sbefifo *sbefifo, int reg, u32 *word) +{ + __be32 raw_word; + int rc; + + rc = fsi_device_read(sbefifo->fsi_dev, reg, &raw_word, + sizeof(raw_word)); + if (rc) + return rc; + + *word = be32_to_cpu(raw_word); + + return 0; +} + +static int sbefifo_regw(struct sbefifo *sbefifo, int reg, u32 word) +{ + __be32 raw_word = cpu_to_be32(word); + + return fsi_device_write(sbefifo->fsi_dev, reg, &raw_word, + sizeof(raw_word)); +} + +static int sbefifo_check_sbe_state(struct sbefifo *sbefifo) +{ + __be32 raw_word; + u32 sbm; + int rc; + + rc = fsi_slave_read(sbefifo->fsi_dev->slave, CFAM_GP_MBOX_SBM_ADDR, + &raw_word, sizeof(raw_word)); + if (rc) + return rc; + sbm = be32_to_cpu(raw_word); + + /* SBE booted at all ? */ + if (!(sbm & CFAM_SBM_SBE_BOOTED)) + return -ESHUTDOWN; + + /* Check its state */ + switch ((sbm & CFAM_SBM_SBE_STATE_MASK) >> CFAM_SBM_SBE_STATE_SHIFT) { + case SBE_STATE_UNKNOWN: + return -ESHUTDOWN; + case SBE_STATE_IPLING: + case SBE_STATE_ISTEP: + case SBE_STATE_MPIPL: + case SBE_STATE_DMT: + return -EBUSY; + case SBE_STATE_RUNTIME: + case SBE_STATE_DUMP: /* Not sure about that one */ + break; + case SBE_STATE_FAILURE: + case SBE_STATE_QUIESCE: + return -ESHUTDOWN; + } + + /* Is there async FFDC available ? Remember it */ + if (sbm & CFAM_SBM_SBE_ASYNC_FFDC) + sbefifo->async_ffdc = true; + + return 0; +} + +/* Don't flip endianness of data to/from FIFO, just pass through. */ +static int sbefifo_down_read(struct sbefifo *sbefifo, __be32 *word) +{ + return fsi_device_read(sbefifo->fsi_dev, SBEFIFO_DOWN, word, + sizeof(*word)); +} + +static int sbefifo_up_write(struct sbefifo *sbefifo, __be32 word) +{ + return fsi_device_write(sbefifo->fsi_dev, SBEFIFO_UP, &word, + sizeof(word)); +} + +static int sbefifo_request_reset(struct sbefifo *sbefifo) +{ + struct device *dev = &sbefifo->fsi_dev->dev; + u32 status, timeout; + int rc; + + dev_dbg(dev, "Requesting FIFO reset\n"); + + /* Mark broken first, will be cleared if reset succeeds */ + sbefifo->broken = true; + + /* Send reset request */ + rc = sbefifo_regw(sbefifo, SBEFIFO_UP | SBEFIFO_REQ_RESET, 1); + if (rc) { + dev_err(dev, "Sending reset request failed, rc=%d\n", rc); + return rc; + } + + /* Wait for it to complete */ + for (timeout = 0; timeout < SBEFIFO_RESET_TIMEOUT; timeout++) { + rc = sbefifo_regr(sbefifo, SBEFIFO_UP | SBEFIFO_STS, &status); + if (rc) { + dev_err(dev, "Failed to read UP fifo status during reset" + " , rc=%d\n", rc); + return rc; + } + + if (!(status & SBEFIFO_STS_RESET_REQ)) { + dev_dbg(dev, "FIFO reset done\n"); + sbefifo->broken = false; + return 0; + } + + msleep(1); + } + dev_err(dev, "FIFO reset timed out\n"); + + return -ETIMEDOUT; +} + +static int sbefifo_cleanup_hw(struct sbefifo *sbefifo) +{ + struct device *dev = &sbefifo->fsi_dev->dev; + u32 up_status, down_status; + bool need_reset = false; + int rc; + + rc = sbefifo_check_sbe_state(sbefifo); + if (rc) { + dev_dbg(dev, "SBE state=%d\n", rc); + return rc; + } + + /* If broken, we don't need to look at status, go straight to reset */ + if (sbefifo->broken) + goto do_reset; + + rc = sbefifo_regr(sbefifo, SBEFIFO_UP | SBEFIFO_STS, &up_status); + if (rc) { + dev_err(dev, "Cleanup: Reading UP status failed, rc=%d\n", rc); + + /* Will try reset again on next attempt at using it */ + sbefifo->broken = true; + return rc; + } + + rc = sbefifo_regr(sbefifo, SBEFIFO_DOWN | SBEFIFO_STS, &down_status); + if (rc) { + dev_err(dev, "Cleanup: Reading DOWN status failed, rc=%d\n", rc); + + /* Will try reset again on next attempt at using it */ + sbefifo->broken = true; + return rc; + } + + /* The FIFO already contains a reset request from the SBE ? */ + if (down_status & SBEFIFO_STS_RESET_REQ) { + dev_info(dev, "Cleanup: FIFO reset request set, resetting\n"); + rc = sbefifo_regw(sbefifo, SBEFIFO_UP, SBEFIFO_PERFORM_RESET); + if (rc) { + sbefifo->broken = true; + dev_err(dev, "Cleanup: Reset reg write failed, rc=%d\n", rc); + return rc; + } + sbefifo->broken = false; + return 0; + } + + /* Parity error on either FIFO ? */ + if ((up_status | down_status) & SBEFIFO_STS_PARITY_ERR) + need_reset = true; + + /* Either FIFO not empty ? */ + if (!((up_status & down_status) & SBEFIFO_STS_EMPTY)) + need_reset = true; + + if (!need_reset) + return 0; + + dev_info(dev, "Cleanup: FIFO not clean (up=0x%08x down=0x%08x)\n", + up_status, down_status); + + do_reset: + + /* Mark broken, will be cleared if/when reset succeeds */ + return sbefifo_request_reset(sbefifo); +} + +static int sbefifo_wait(struct sbefifo *sbefifo, bool up, + u32 *status, unsigned long timeout) +{ + struct device *dev = &sbefifo->fsi_dev->dev; + unsigned long end_time; + bool ready = false; + u32 addr, sts = 0; + int rc; + + dev_vdbg(dev, "Wait on %s fifo...\n", up ? "up" : "down"); + + addr = (up ? SBEFIFO_UP : SBEFIFO_DOWN) | SBEFIFO_STS; + + end_time = jiffies + timeout; + while (!time_after(jiffies, end_time)) { + cond_resched(); + rc = sbefifo_regr(sbefifo, addr, &sts); + if (rc < 0) { + dev_err(dev, "FSI error %d reading status register\n", rc); + return rc; + } + if (!up && sbefifo_parity_err(sts)) { + dev_err(dev, "Parity error in DOWN FIFO\n"); + return -ENXIO; + } + ready = !(up ? sbefifo_full(sts) : sbefifo_empty(sts)); + if (ready) + break; + } + if (!ready) { + dev_err(dev, "%s FIFO Timeout ! status=%08x\n", up ? "UP" : "DOWN", sts); + return -ETIMEDOUT; + } + dev_vdbg(dev, "End of wait status: %08x\n", sts); + + *status = sts; + + return 0; +} + +static int sbefifo_send_command(struct sbefifo *sbefifo, + const __be32 *command, size_t cmd_len) +{ + struct device *dev = &sbefifo->fsi_dev->dev; + size_t len, chunk, vacant = 0, remaining = cmd_len; + unsigned long timeout; + u32 status; + int rc; + + dev_vdbg(dev, "sending command (%zd words, cmd=%04x)\n", + cmd_len, be32_to_cpu(command[1])); + + /* As long as there's something to send */ + timeout = msecs_to_jiffies(SBEFIFO_TIMEOUT_START_CMD); + while (remaining) { + /* Wait for room in the FIFO */ + rc = sbefifo_wait(sbefifo, true, &status, timeout); + if (rc < 0) + return rc; + timeout = msecs_to_jiffies(SBEFIFO_TIMEOUT_IN_CMD); + + vacant = sbefifo_vacant(status); + len = chunk = min(vacant, remaining); + + dev_vdbg(dev, " status=%08x vacant=%zd chunk=%zd\n", + status, vacant, chunk); + + /* Write as much as we can */ + while (len--) { + rc = sbefifo_up_write(sbefifo, *(command++)); + if (rc) { + dev_err(dev, "FSI error %d writing UP FIFO\n", rc); + return rc; + } + } + remaining -= chunk; + vacant -= chunk; + } + + /* If there's no room left, wait for some to write EOT */ + if (!vacant) { + rc = sbefifo_wait(sbefifo, true, &status, timeout); + if (rc) + return rc; + } + + /* Send an EOT */ + rc = sbefifo_regw(sbefifo, SBEFIFO_UP | SBEFIFO_EOT_RAISE, 0); + if (rc) + dev_err(dev, "FSI error %d writing EOT\n", rc); + return rc; +} + +static int sbefifo_read_response(struct sbefifo *sbefifo, struct iov_iter *response) +{ + struct device *dev = &sbefifo->fsi_dev->dev; + u32 data, status, eot_set; + unsigned long timeout; + bool overflow = false; + size_t len; + int rc; + + dev_vdbg(dev, "reading response, buflen = %zd\n", iov_iter_count(response)); + + timeout = msecs_to_jiffies(SBEFIFO_TIMEOUT_START_RSP); + for (;;) { + /* Grab FIFO status (this will handle parity errors) */ + rc = sbefifo_wait(sbefifo, false, &status, timeout); + if (rc < 0) + return rc; + timeout = msecs_to_jiffies(SBEFIFO_TIMEOUT_IN_RSP); + + /* Decode status */ + len = sbefifo_populated(status); + eot_set = sbefifo_eot_set(status); + + dev_vdbg(dev, " chunk size %zd eot_set=0x%x\n", len, eot_set); + + /* Go through the chunk */ + while(len--) { + /* Read the data */ + rc = sbefifo_down_read(sbefifo, &data); + if (rc < 0) + return rc; + + /* Was it an EOT ? */ + if (eot_set & 0x80) { + /* + * There should be nothing else in the FIFO, + * if there is, mark broken, this will force + * a reset on next use, but don't fail the + * command. + */ + if (len) { + dev_warn(dev, "FIFO read hit" + " EOT with still %zd data\n", + len); + sbefifo->broken = true; + } + + /* We are done */ + rc = sbefifo_regw(sbefifo, + SBEFIFO_DOWN | SBEFIFO_EOT_ACK, 0); + + /* + * If that write fail, still complete the request but mark + * the fifo as broken for subsequent reset (not much else + * we can do here). + */ + if (rc) { + dev_err(dev, "FSI error %d ack'ing EOT\n", rc); + sbefifo->broken = true; + } + + /* Tell whether we overflowed */ + return overflow ? -EOVERFLOW : 0; + } + + /* Store it if there is room */ + if (iov_iter_count(response) >= sizeof(__be32)) { + if (copy_to_iter(&data, sizeof(__be32), response) < sizeof(__be32)) + return -EFAULT; + } else { + dev_vdbg(dev, "Response overflowed !\n"); + + overflow = true; + } + + /* Next EOT bit */ + eot_set <<= 1; + } + } + /* Shouldn't happen */ + return -EIO; +} + +static int sbefifo_do_command(struct sbefifo *sbefifo, + const __be32 *command, size_t cmd_len, + struct iov_iter *response) +{ + /* Try sending the command */ + int rc = sbefifo_send_command(sbefifo, command, cmd_len); + if (rc) + return rc; + + /* Now, get the response */ + return sbefifo_read_response(sbefifo, response); +} + +static void sbefifo_collect_async_ffdc(struct sbefifo *sbefifo) +{ + struct device *dev = &sbefifo->fsi_dev->dev; + struct iov_iter ffdc_iter; + struct kvec ffdc_iov; + __be32 *ffdc; + size_t ffdc_sz; + u32 cmd[2]; + int rc; + + sbefifo->async_ffdc = false; + ffdc = vmalloc(SBEFIFO_MAX_FFDC_SIZE); + if (!ffdc) { + dev_err(dev, "Failed to allocate SBE FFDC buffer\n"); + return; + } + ffdc_iov.iov_base = ffdc; + ffdc_iov.iov_len = SBEFIFO_MAX_FFDC_SIZE;; + iov_iter_kvec(&ffdc_iter, WRITE | ITER_KVEC, &ffdc_iov, 1, SBEFIFO_MAX_FFDC_SIZE); + cmd[0] = cpu_to_be32(2); + cmd[1] = cpu_to_be32(SBEFIFO_CMD_GET_SBE_FFDC); + rc = sbefifo_do_command(sbefifo, cmd, 2, &ffdc_iter); + if (rc != 0) { + dev_err(dev, "Error %d retrieving SBE FFDC\n", rc); + goto bail; + } + ffdc_sz = SBEFIFO_MAX_FFDC_SIZE - iov_iter_count(&ffdc_iter); + ffdc_sz /= sizeof(__be32); + rc = sbefifo_parse_status(dev, SBEFIFO_CMD_GET_SBE_FFDC, ffdc, + ffdc_sz, &ffdc_sz); + if (rc != 0) { + dev_err(dev, "Error %d decoding SBE FFDC\n", rc); + goto bail; + } + if (ffdc_sz > 0) + sbefifo_dump_ffdc(dev, ffdc, ffdc_sz, true); + bail: + vfree(ffdc); + +} + +static int __sbefifo_submit(struct sbefifo *sbefifo, + const __be32 *command, size_t cmd_len, + struct iov_iter *response) +{ + struct device *dev = &sbefifo->fsi_dev->dev; + int rc; + + if (cmd_len < 2 || be32_to_cpu(command[0]) != cmd_len) { + dev_vdbg(dev, "Invalid command len %zd (header: %d)\n", + cmd_len, be32_to_cpu(command[0])); + return -EINVAL; + } + + /* First ensure the HW is in a clean state */ + rc = sbefifo_cleanup_hw(sbefifo); + if (rc) + return rc; + + /* Look for async FFDC first if any */ + if (sbefifo->async_ffdc) + sbefifo_collect_async_ffdc(sbefifo); + + rc = sbefifo_do_command(sbefifo, command, cmd_len, response); + if (rc != 0 && rc != -EOVERFLOW) + goto fail; + return rc; + fail: + /* + * On failure, attempt a reset. Ignore the result, it will mark + * the fifo broken if the reset fails + */ + sbefifo_request_reset(sbefifo); + + /* Return original error */ + return rc; +} + +/** + * sbefifo_submit() - Submit and SBE fifo command and receive response + * @dev: The sbefifo device + * @command: The raw command data + * @cmd_len: The command size (in 32-bit words) + * @response: The output response buffer + * @resp_len: In: Response buffer size, Out: Response size + * + * This will perform the entire operation. If the reponse buffer + * overflows, returns -EOVERFLOW + */ +int sbefifo_submit(struct device *dev, const __be32 *command, size_t cmd_len, + __be32 *response, size_t *resp_len) +{ + struct sbefifo *sbefifo = dev_get_drvdata(dev); + struct iov_iter resp_iter; + struct kvec resp_iov; + size_t rbytes; + int rc; + + if (!dev || !sbefifo) + return -ENODEV; + if (WARN_ON_ONCE(sbefifo->magic != SBEFIFO_MAGIC)) + return -ENODEV; + if (!resp_len || !command || !response || cmd_len > SBEFIFO_MAX_CMD_LEN) + return -EINVAL; + + /* Prepare iov iterator */ + rbytes = (*resp_len) * sizeof(__be32); + resp_iov.iov_base = response; + resp_iov.iov_len = rbytes; + iov_iter_kvec(&resp_iter, WRITE | ITER_KVEC, &resp_iov, 1, rbytes); + + /* Perform the command */ + mutex_lock(&sbefifo->lock); + rc = __sbefifo_submit(sbefifo, command, cmd_len, &resp_iter); + mutex_unlock(&sbefifo->lock); + + /* Extract the response length */ + rbytes -= iov_iter_count(&resp_iter); + *resp_len = rbytes / sizeof(__be32); + + return rc; +} +EXPORT_SYMBOL_GPL(sbefifo_submit); + +/* + * Char device interface + */ +static int sbefifo_user_open(struct inode *inode, struct file *file) +{ + struct sbefifo *sbefifo = container_of(file->private_data, + struct sbefifo, mdev); + struct sbefifo_user *user; + + user = kzalloc(sizeof(struct sbefifo_user), GFP_KERNEL); + if (!user) + return -ENOMEM; + + file->private_data = user; + user->sbefifo = sbefifo; + user->pending_cmd = (void *)__get_free_page(GFP_KERNEL); + if (!user->pending_cmd) { + kfree(user); + return -ENOMEM; + } + mutex_init(&user->file_lock); + + return 0; +} + +static ssize_t sbefifo_user_read(struct file *file, char __user *buf, + size_t len, loff_t *offset) +{ + struct sbefifo_user *user = file->private_data; + struct sbefifo *sbefifo; + struct iov_iter resp_iter; + struct iovec resp_iov; + size_t cmd_len; + int rc; + + if (!user) + return -EINVAL; + sbefifo = user->sbefifo; + if (len & 3) + return -EINVAL; + + mutex_lock(&user->file_lock); + + /* Cronus relies on -EAGAIN after a short read */ + if (user->pending_len == 0) { + rc = -EAGAIN; + goto bail; + } + if (user->pending_len < 8) { + rc = -EINVAL; + goto bail; + } + cmd_len = user->pending_len >> 2; + + /* Prepare iov iterator */ + resp_iov.iov_base = buf; + resp_iov.iov_len = len; + iov_iter_init(&resp_iter, WRITE, &resp_iov, 1, len); + + /* Perform the command */ + mutex_lock(&sbefifo->lock); + rc = __sbefifo_submit(sbefifo, user->pending_cmd, cmd_len, &resp_iter); + mutex_unlock(&sbefifo->lock); + if (rc < 0) + goto bail; + + /* Extract the response length */ + rc = len - iov_iter_count(&resp_iter); + bail: + user->pending_len = 0; + mutex_unlock(&user->file_lock); + return rc; +} + +static ssize_t sbefifo_user_write(struct file *file, const char __user *buf, + size_t len, loff_t *offset) +{ + struct sbefifo_user *user = file->private_data; + struct sbefifo *sbefifo; + int rc = len; + + if (!user) + return -EINVAL; + sbefifo = user->sbefifo; + if (len > SBEFIFO_MAX_CMD_LEN) + return -EINVAL; + if (len & 3) + return -EINVAL; + + mutex_lock(&user->file_lock); + + /* Copy the command into the staging buffer */ + if (copy_from_user(user->pending_cmd, buf, len)) { + rc = -EFAULT; + goto bail; + } + + /* Check for the magic reset command */ + if (len == 4 && be32_to_cpu(*(__be32 *)user->pending_cmd) == + SBEFIFO_RESET_MAGIC) { + + /* Clear out any pending command */ + user->pending_len = 0; + + /* Trigger reset request */ + mutex_lock(&sbefifo->lock); + rc = sbefifo_request_reset(user->sbefifo); + mutex_unlock(&sbefifo->lock); + if (rc == 0) + rc = 4; + goto bail; + } + + /* Update the staging buffer size */ + user->pending_len = len; + bail: + mutex_unlock(&user->file_lock); + + /* And that's it, we'll issue the command on a read */ + return rc; +} + +static int sbefifo_user_release(struct inode *inode, struct file *file) +{ + struct sbefifo_user *user = file->private_data; + + if (!user) + return -EINVAL; + + free_page((unsigned long)user->pending_cmd); + kfree(user); + + return 0; +} + +static const struct file_operations sbefifo_fops = { + .owner = THIS_MODULE, + .open = sbefifo_user_open, + .read = sbefifo_user_read, + .write = sbefifo_user_write, + .release = sbefifo_user_release, +}; + +/* + * Probe/remove + */ + +static int sbefifo_probe(struct device *dev) +{ + struct fsi_device *fsi_dev = to_fsi_dev(dev); + struct sbefifo *sbefifo; + struct device_node *np; + struct platform_device *child; + char child_name[32]; + int rc, child_idx = 0; + + dev_dbg(dev, "Found sbefifo device\n"); + + sbefifo = devm_kzalloc(dev, sizeof(*sbefifo), GFP_KERNEL); + if (!sbefifo) + return -ENOMEM; + sbefifo->magic = SBEFIFO_MAGIC; + sbefifo->fsi_dev = fsi_dev; + mutex_init(&sbefifo->lock); + + /* + * Try cleaning up the FIFO. If this fails, we still register the + * driver and will try cleaning things up again on the next access. + */ + rc = sbefifo_cleanup_hw(sbefifo); + if (rc && rc != -ESHUTDOWN) + dev_err(dev, "Initial HW cleanup failed, will retry later\n"); + + sbefifo->idx = ida_simple_get(&sbefifo_ida, 1, INT_MAX, GFP_KERNEL); + snprintf(sbefifo->name, sizeof(sbefifo->name), "sbefifo%d", + sbefifo->idx); + + dev_set_drvdata(dev, sbefifo); + + /* Create misc chardev for userspace access */ + sbefifo->mdev.minor = MISC_DYNAMIC_MINOR; + sbefifo->mdev.fops = &sbefifo_fops; + sbefifo->mdev.name = sbefifo->name; + sbefifo->mdev.parent = dev; + rc = misc_register(&sbefifo->mdev); + if (rc) { + dev_err(dev, "Failed to register miscdevice: %d\n", rc); + ida_simple_remove(&sbefifo_ida, sbefifo->idx); + return rc; + } + + /* Create platform devs for dts child nodes (occ, etc) */ + for_each_available_child_of_node(dev->of_node, np) { + snprintf(child_name, sizeof(child_name), "%s-dev%d", + sbefifo->name, child_idx++); + child = of_platform_device_create(np, child_name, dev); + if (!child) + dev_warn(dev, "failed to create child %s dev\n", + child_name); + } + + return 0; +} + +static int sbefifo_unregister_child(struct device *dev, void *data) +{ + struct platform_device *child = to_platform_device(dev); + + of_device_unregister(child); + if (dev->of_node) + of_node_clear_flag(dev->of_node, OF_POPULATED); + + return 0; +} + +static int sbefifo_remove(struct device *dev) +{ + struct sbefifo *sbefifo = dev_get_drvdata(dev); + + dev_dbg(dev, "Removing sbefifo device...\n"); + + misc_deregister(&sbefifo->mdev); + device_for_each_child(dev, NULL, sbefifo_unregister_child); + + ida_simple_remove(&sbefifo_ida, sbefifo->idx); + + return 0; +} + +static struct fsi_device_id sbefifo_ids[] = { + { + .engine_type = FSI_ENGID_SBE, + .version = FSI_VERSION_ANY, + }, + { 0 } +}; + +static struct fsi_driver sbefifo_drv = { + .id_table = sbefifo_ids, + .drv = { + .name = DEVICE_NAME, + .bus = &fsi_bus_type, + .probe = sbefifo_probe, + .remove = sbefifo_remove, + } +}; + +static int sbefifo_init(void) +{ + return fsi_driver_register(&sbefifo_drv); +} + +static void sbefifo_exit(void) +{ + fsi_driver_unregister(&sbefifo_drv); + + ida_destroy(&sbefifo_ida); +} + +module_init(sbefifo_init); +module_exit(sbefifo_exit); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Brad Bishop "); +MODULE_AUTHOR("Eddie James "); +MODULE_AUTHOR("Andrew Jeffery "); +MODULE_AUTHOR("Benjamin Herrenschmidt "); +MODULE_DESCRIPTION("Linux device interface to the POWER Self Boot Engine"); diff --git a/include/linux/fsi-sbefifo.h b/include/linux/fsi-sbefifo.h new file mode 100644 index 000000000000..13f9ebeaa25e --- /dev/null +++ b/include/linux/fsi-sbefifo.h @@ -0,0 +1,33 @@ +/* + * SBEFIFO FSI Client device driver + * + * Copyright (C) IBM Corporation 2017 + * + * 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 + * MERGCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef LINUX_FSI_SBEFIFO_H +#define LINUX_FSI_SBEFIFO_H + +#define SBEFIFO_CMD_PUT_OCC_SRAM 0xa404 +#define SBEFIFO_CMD_GET_OCC_SRAM 0xa403 +#define SBEFIFO_CMD_GET_SBE_FFDC 0xa801 + +#define SBEFIFO_MAX_FFDC_SIZE 0x2000 + +struct device; + +int sbefifo_submit(struct device *dev, const __be32 *command, size_t cmd_len, + __be32 *response, size_t *resp_len); + +int sbefifo_parse_status(struct device *dev, u16 cmd, __be32 *response, + size_t resp_len, size_t *data_len); + +#endif /* LINUX_FSI_SBEFIFO_H */ -- cgit v1.2.3 From a9ef97d098c5bfb000a3a7454efd0304127b6b0b Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Thu, 14 Jun 2018 14:01:46 +1000 Subject: fsi: sbefifo: Remove unneeded semicolon Spotted by kbuild-test-bot Signed-off-by: Benjamin Herrenschmidt --- drivers/fsi/fsi-sbefifo.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/fsi/fsi-sbefifo.c b/drivers/fsi/fsi-sbefifo.c index 9b8b6b346af6..e6882a2b1709 100644 --- a/drivers/fsi/fsi-sbefifo.c +++ b/drivers/fsi/fsi-sbefifo.c @@ -629,7 +629,7 @@ static void sbefifo_collect_async_ffdc(struct sbefifo *sbefifo) return; } ffdc_iov.iov_base = ffdc; - ffdc_iov.iov_len = SBEFIFO_MAX_FFDC_SIZE;; + ffdc_iov.iov_len = SBEFIFO_MAX_FFDC_SIZE; iov_iter_kvec(&ffdc_iter, WRITE | ITER_KVEC, &ffdc_iov, 1, SBEFIFO_MAX_FFDC_SIZE); cmd[0] = cpu_to_be32(2); cmd[1] = cpu_to_be32(SBEFIFO_CMD_GET_SBE_FFDC); -- cgit v1.2.3 From 2992513877d9eada1443d2d70c2c89b77ac2cf2c Mon Sep 17 00:00:00 2001 From: Joel Stanley Date: Mon, 18 Jun 2018 13:13:33 +0930 Subject: fsi: sbefifo: Fix sparse warnings fsi-sbefifo.c:547:58: warning: incorrect type in argument 2 (different base types) fsi-sbefifo.c:547:58: expected restricted __be32 [usertype] *word fsi-sbefifo.c:547:58: got unsigned int * fsi-sbefifo.c:635:16: warning: incorrect type in assignment (different base types) fsi-sbefifo.c:635:16: expected unsigned int [unsigned] fsi-sbefifo.c:635:16: got restricted __be32 [usertype] fsi-sbefifo.c:636:16: warning: incorrect type in assignment (different base types) fsi-sbefifo.c:636:16: expected unsigned int [unsigned] fsi-sbefifo.c:636:16: got restricted __be32 [usertype] Signed-off-by: Joel Stanley Signed-off-by: Benjamin Herrenschmidt --- drivers/fsi/fsi-sbefifo.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/fsi/fsi-sbefifo.c b/drivers/fsi/fsi-sbefifo.c index e6882a2b1709..4f076fd2939c 100644 --- a/drivers/fsi/fsi-sbefifo.c +++ b/drivers/fsi/fsi-sbefifo.c @@ -519,9 +519,10 @@ static int sbefifo_send_command(struct sbefifo *sbefifo, static int sbefifo_read_response(struct sbefifo *sbefifo, struct iov_iter *response) { struct device *dev = &sbefifo->fsi_dev->dev; - u32 data, status, eot_set; + u32 status, eot_set; unsigned long timeout; bool overflow = false; + __be32 data; size_t len; int rc; @@ -619,7 +620,7 @@ static void sbefifo_collect_async_ffdc(struct sbefifo *sbefifo) struct kvec ffdc_iov; __be32 *ffdc; size_t ffdc_sz; - u32 cmd[2]; + __be32 cmd[2]; int rc; sbefifo->async_ffdc = false; -- cgit v1.2.3 From fbdb5eac5ca64350333b0aa38c9dca6973d6d3a3 Mon Sep 17 00:00:00 2001 From: Joel Stanley Date: Mon, 18 Jun 2018 13:13:34 +0930 Subject: fsi: master-hub: Fix sparse warnings fsi-master-hub.c:128:13: warning: incorrect type in assignment (different base types) fsi-master-hub.c:128:13: expected unsigned int [unsigned] [usertype] cmd fsi-master-hub.c:128:13: got restricted __be32 [usertype] fsi-master-hub.c:208:13: warning: incorrect type in assignment (different base types) fsi-master-hub.c:208:13: expected restricted __be32 [addressable] [assigned] [usertype] reg fsi-master-hub.c:208:13: got int Signed-off-by: Joel Stanley Signed-off-by: Benjamin Herrenschmidt --- drivers/fsi/fsi-master-hub.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/fsi/fsi-master-hub.c b/drivers/fsi/fsi-master-hub.c index 5885fc4a1ef0..b3c1e9debcf2 100644 --- a/drivers/fsi/fsi-master-hub.c +++ b/drivers/fsi/fsi-master-hub.c @@ -122,7 +122,8 @@ static int hub_master_write(struct fsi_master *master, int link, static int hub_master_break(struct fsi_master *master, int link) { - uint32_t addr, cmd; + uint32_t addr; + __be32 cmd; addr = 0x4; cmd = cpu_to_be32(0xc0de0000); @@ -205,7 +206,7 @@ static int hub_master_init(struct fsi_master_hub *hub) if (rc) return rc; - reg = ~0; + reg = cpu_to_be32(~0); rc = fsi_device_write(dev, FSI_MSENP0, ®, sizeof(reg)); if (rc) return rc; -- cgit v1.2.3 From 11454d6dc81813edb085b63b43b2e4df5e006218 Mon Sep 17 00:00:00 2001 From: Joel Stanley Date: Mon, 18 Jun 2018 13:13:35 +0930 Subject: fsi: core: Fix sparse warnings fsi-core.c:210:9: warning: cast to restricted __be32 fsi-core.c:210:9: warning: cast to restricted __be32 fsi-core.c:210:9: warning: cast to restricted __be32 fsi-core.c:210:9: warning: cast to restricted __be32 fsi-core.c:210:9: warning: cast to restricted __be32 fsi-core.c:210:9: warning: cast to restricted __be32 fsi-core.c:210:9: warning: cast to restricted __be32 fsi-core.c:210:9: warning: cast to restricted __be32 fsi-core.c:210:9: warning: cast to restricted __be32 fsi-core.c:210:9: warning: cast to restricted __be32 fsi-core.c:210:9: warning: cast to restricted __be32 fsi-core.c:210:9: warning: cast to restricted __be32 fsi-core.c:606:15: warning: incorrect type in assignment (different base types) fsi-core.c:606:15: expected unsigned int [unsigned] [assigned] [usertype] smode fsi-core.c:606:15: got restricted __be32 [usertype] fsi-core.c:492:28: warning: expression using sizeof(void) fsi-core.c:520:29: warning: expression using sizeof(void) fsi-core.c:682:19: warning: cast to restricted __be32 fsi-core.c:682:19: warning: cast to restricted __be32 fsi-core.c:682:19: warning: cast to restricted __be32 fsi-core.c:682:19: warning: cast to restricted __be32 fsi-core.c:682:19: warning: cast to restricted __be32 fsi-core.c:682:19: warning: cast to restricted __be32 fsi-core.c:706:24: warning: incorrect type in assignment (different base types) fsi-core.c:706:24: expected unsigned int [unsigned] [usertype] llmode fsi-core.c:706:24: got restricted __be32 [usertype] Signed-off-by: Joel Stanley Signed-off-by: Benjamin Herrenschmidt --- drivers/fsi/fsi-core.c | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/drivers/fsi/fsi-core.c b/drivers/fsi/fsi-core.c index 4c03d6933646..a3f0d41f4c59 100644 --- a/drivers/fsi/fsi-core.c +++ b/drivers/fsi/fsi-core.c @@ -190,7 +190,7 @@ static int fsi_slave_calc_addr(struct fsi_slave *slave, uint32_t *addrp, static int fsi_slave_report_and_clear_errors(struct fsi_slave *slave) { struct fsi_master *master = slave->master; - uint32_t irq, stat; + __be32 irq, stat; int rc, link; uint8_t id; @@ -390,7 +390,6 @@ static struct device_node *fsi_device_find_of_node(struct fsi_device *dev) static int fsi_slave_scan(struct fsi_slave *slave) { uint32_t engine_addr; - uint32_t conf; int rc, i; /* @@ -404,15 +403,17 @@ static int fsi_slave_scan(struct fsi_slave *slave) for (i = 2; i < engine_page_size / sizeof(uint32_t); i++) { uint8_t slots, version, type, crc; struct fsi_device *dev; + uint32_t conf; + __be32 data; - rc = fsi_slave_read(slave, (i + 1) * sizeof(conf), - &conf, sizeof(conf)); + rc = fsi_slave_read(slave, (i + 1) * sizeof(data), + &data, sizeof(data)); if (rc) { dev_warn(&slave->dev, "error reading slave registers\n"); return -1; } - conf = be32_to_cpu(conf); + conf = be32_to_cpu(data); crc = crc4(0, conf, 32); if (crc) { @@ -597,15 +598,16 @@ static uint32_t fsi_slave_smode(int id) static int fsi_slave_set_smode(struct fsi_master *master, int link, int id) { uint32_t smode; + __be32 data; /* set our smode register with the slave ID field to 0; this enables * extended slave addressing */ smode = fsi_slave_smode(id); - smode = cpu_to_be32(smode); + data = cpu_to_be32(smode); return fsi_master_write(master, link, id, FSI_SLAVE_BASE + FSI_SMODE, - &smode, sizeof(smode)); + &data, sizeof(data)); } static void fsi_slave_release(struct device *dev) @@ -661,9 +663,10 @@ static struct device_node *fsi_slave_find_of_node(struct fsi_master *master, static int fsi_slave_init(struct fsi_master *master, int link, uint8_t id) { - uint32_t chip_id, llmode; + uint32_t chip_id; struct fsi_slave *slave; uint8_t crc; + __be32 data, llmode; int rc; /* Currently, we only support single slaves on a link, and use the @@ -672,13 +675,13 @@ static int fsi_slave_init(struct fsi_master *master, int link, uint8_t id) if (id != 0) return -EINVAL; - rc = fsi_master_read(master, link, id, 0, &chip_id, sizeof(chip_id)); + rc = fsi_master_read(master, link, id, 0, &data, sizeof(data)); if (rc) { dev_dbg(&master->dev, "can't read slave %02x:%02x %d\n", link, id, rc); return -ENODEV; } - chip_id = be32_to_cpu(chip_id); + chip_id = be32_to_cpu(data); crc = crc4(0, chip_id, 32); if (crc) { -- cgit v1.2.3 From 162c3946734d1f1950b413bbb3182d76d5be484b Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Tue, 12 Jun 2018 15:19:07 +1000 Subject: fsi: scom: Add mutex around FSI2PIB accesses Otherwise, multiple clients can open the driver and attempt to access the PIB at the same time, thus clobbering each other in the process. Signed-off-by: Benjamin Herrenschmidt Reviewed-by: Eddie James --- drivers/fsi/fsi-scom.c | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/drivers/fsi/fsi-scom.c b/drivers/fsi/fsi-scom.c index c8eb5e5b94a7..3cba0eb645e1 100644 --- a/drivers/fsi/fsi-scom.c +++ b/drivers/fsi/fsi-scom.c @@ -37,6 +37,7 @@ struct scom_device { struct list_head link; struct fsi_device *fsi_dev; struct miscdevice mdev; + struct mutex lock; char name[32]; int idx; }; @@ -53,21 +54,26 @@ static int put_scom(struct scom_device *scom_dev, uint64_t value, int rc; uint32_t data; + mutex_lock(&scom_dev->lock); + data = cpu_to_be32((value >> 32) & 0xffffffff); rc = fsi_device_write(scom_dev->fsi_dev, SCOM_DATA0_REG, &data, sizeof(uint32_t)); if (rc) - return rc; + goto bail; data = cpu_to_be32(value & 0xffffffff); rc = fsi_device_write(scom_dev->fsi_dev, SCOM_DATA1_REG, &data, sizeof(uint32_t)); if (rc) - return rc; + goto bail; data = cpu_to_be32(SCOM_WRITE_CMD | addr); - return fsi_device_write(scom_dev->fsi_dev, SCOM_CMD_REG, &data, + rc = fsi_device_write(scom_dev->fsi_dev, SCOM_CMD_REG, &data, sizeof(uint32_t)); + bail: + mutex_unlock(&scom_dev->lock); + return rc; } static int get_scom(struct scom_device *scom_dev, uint64_t *value, @@ -76,27 +82,31 @@ static int get_scom(struct scom_device *scom_dev, uint64_t *value, uint32_t result, data; int rc; + + mutex_lock(&scom_dev->lock); *value = 0ULL; data = cpu_to_be32(addr); rc = fsi_device_write(scom_dev->fsi_dev, SCOM_CMD_REG, &data, sizeof(uint32_t)); if (rc) - return rc; + goto bail; rc = fsi_device_read(scom_dev->fsi_dev, SCOM_DATA0_REG, &result, sizeof(uint32_t)); if (rc) - return rc; + goto bail; *value |= (uint64_t)cpu_to_be32(result) << 32; rc = fsi_device_read(scom_dev->fsi_dev, SCOM_DATA1_REG, &result, sizeof(uint32_t)); if (rc) - return rc; + goto bail; *value |= cpu_to_be32(result); - return 0; + bail: + mutex_unlock(&scom_dev->lock); + return rc; } static ssize_t scom_read(struct file *filep, char __user *buf, size_t len, @@ -183,6 +193,7 @@ static int scom_probe(struct device *dev) if (!scom) return -ENOMEM; + mutex_init(&scom->lock); scom->idx = ida_simple_get(&scom_ida, 1, INT_MAX, GFP_KERNEL); snprintf(scom->name, sizeof(scom->name), "scom%d", scom->idx); scom->fsi_dev = fsi_dev; -- cgit v1.2.3 From bd21336457922280ac0eccdb3aeec14a5bf1391b Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Tue, 12 Jun 2018 15:19:08 +1000 Subject: fsi: scom: Whitespace fixes No functional changes Signed-off-by: Benjamin Herrenschmidt Reviewed-by: Eddie James --- drivers/fsi/fsi-scom.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/fsi/fsi-scom.c b/drivers/fsi/fsi-scom.c index 3cba0eb645e1..8a608db0aa07 100644 --- a/drivers/fsi/fsi-scom.c +++ b/drivers/fsi/fsi-scom.c @@ -49,7 +49,7 @@ static struct list_head scom_devices; static DEFINE_IDA(scom_ida); static int put_scom(struct scom_device *scom_dev, uint64_t value, - uint32_t addr) + uint32_t addr) { int rc; uint32_t data; @@ -77,7 +77,7 @@ static int put_scom(struct scom_device *scom_dev, uint64_t value, } static int get_scom(struct scom_device *scom_dev, uint64_t *value, - uint32_t addr) + uint32_t addr) { uint32_t result, data; int rc; @@ -110,7 +110,7 @@ static int get_scom(struct scom_device *scom_dev, uint64_t *value, } static ssize_t scom_read(struct file *filep, char __user *buf, size_t len, - loff_t *offset) + loff_t *offset) { int rc; struct miscdevice *mdev = @@ -136,7 +136,7 @@ static ssize_t scom_read(struct file *filep, char __user *buf, size_t len, } static ssize_t scom_write(struct file *filep, const char __user *buf, - size_t len, loff_t *offset) + size_t len, loff_t *offset) { int rc; struct miscdevice *mdev = filep->private_data; -- cgit v1.2.3 From 5a3c2f7656d0332eca310c74fb3641da0e45df17 Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Tue, 12 Jun 2018 15:19:09 +1000 Subject: fsi: scom: Fixup endian annotations Use the proper annotated type __be32 and fixup the accessor used for get_scom() Signed-off-by: Benjamin Herrenschmidt Reviewed-by: Eddie James --- drivers/fsi/fsi-scom.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/drivers/fsi/fsi-scom.c b/drivers/fsi/fsi-scom.c index 8a608db0aa07..6ddfb6021420 100644 --- a/drivers/fsi/fsi-scom.c +++ b/drivers/fsi/fsi-scom.c @@ -51,8 +51,8 @@ static DEFINE_IDA(scom_ida); static int put_scom(struct scom_device *scom_dev, uint64_t value, uint32_t addr) { + __be32 data; int rc; - uint32_t data; mutex_lock(&scom_dev->lock); @@ -79,7 +79,7 @@ static int put_scom(struct scom_device *scom_dev, uint64_t value, static int get_scom(struct scom_device *scom_dev, uint64_t *value, uint32_t addr) { - uint32_t result, data; + __be32 result, data; int rc; @@ -96,14 +96,13 @@ static int get_scom(struct scom_device *scom_dev, uint64_t *value, if (rc) goto bail; - *value |= (uint64_t)cpu_to_be32(result) << 32; + *value |= (uint64_t)be32_to_cpu(result) << 32; rc = fsi_device_read(scom_dev->fsi_dev, SCOM_DATA1_REG, &result, sizeof(uint32_t)); if (rc) goto bail; - *value |= cpu_to_be32(result); - + *value |= be32_to_cpu(result); bail: mutex_unlock(&scom_dev->lock); return rc; -- cgit v1.2.3 From f143304442f4b273f28343910f79d0221984b096 Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Tue, 12 Jun 2018 15:19:10 +1000 Subject: fsi: scom: Add register definitions Add a few more register and bit definitions, also define and use SCOM_READ_CMD (which is 0 but it makes the code clearer) Signed-off-by: Benjamin Herrenschmidt Reviewed-by: Eddie James --- drivers/fsi/fsi-scom.c | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/drivers/fsi/fsi-scom.c b/drivers/fsi/fsi-scom.c index 6ddfb6021420..e98573ecdae1 100644 --- a/drivers/fsi/fsi-scom.c +++ b/drivers/fsi/fsi-scom.c @@ -30,8 +30,25 @@ #define SCOM_DATA0_REG 0x00 #define SCOM_DATA1_REG 0x04 #define SCOM_CMD_REG 0x08 +#define SCOM_FSI2PIB_RESET_REG 0x18 +#define SCOM_STATUS_REG 0x1C /* Read */ +#define SCOM_PIB_RESET_REG 0x1C /* Write */ +/* Command register */ #define SCOM_WRITE_CMD 0x80000000 +#define SCOM_READ_CMD 0x00000000 + +/* Status register bits */ +#define SCOM_STATUS_ERR_SUMMARY 0x80000000 +#define SCOM_STATUS_PROTECTION 0x01000000 +#define SCOM_STATUS_PIB_ABORT 0x00100000 +#define SCOM_STATUS_PIB_RESP_MASK 0x00007000 +#define SCOM_STATUS_PIB_RESP_SHIFT 12 + +#define SCOM_STATUS_ANY_ERR (SCOM_STATUS_ERR_SUMMARY | \ + SCOM_STATUS_PROTECTION | \ + SCOM_STATUS_PIB_ABORT | \ + SCOM_STATUS_PIB_RESP_MASK) struct scom_device { struct list_head link; @@ -85,7 +102,7 @@ static int get_scom(struct scom_device *scom_dev, uint64_t *value, mutex_lock(&scom_dev->lock); *value = 0ULL; - data = cpu_to_be32(addr); + data = cpu_to_be32(SCOM_READ_CMD | addr); rc = fsi_device_write(scom_dev->fsi_dev, SCOM_CMD_REG, &data, sizeof(uint32_t)); if (rc) -- cgit v1.2.3 From 6b293258cded9c8ee44cce4081d9170d6d1b5f5d Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Tue, 12 Jun 2018 15:19:11 +1000 Subject: fsi: scom: Major overhaul This was too hard to split ... this adds a number of features to the SCOM user interface: - Support for indirect SCOMs - read()/write() interface now handle errors and retries - New ioctl() "raw" interface for use by debuggers Signed-off-by: Benjamin Herrenschmidt Reviewed-by: Eddie James Reviewed-by: Alistair Popple --- drivers/fsi/fsi-scom.c | 424 +++++++++++++++++++++++++++++++++++++++++++---- include/uapi/linux/fsi.h | 58 +++++++ 2 files changed, 452 insertions(+), 30 deletions(-) create mode 100644 include/uapi/linux/fsi.h diff --git a/drivers/fsi/fsi-scom.c b/drivers/fsi/fsi-scom.c index e98573ecdae1..39c74351f1bf 100644 --- a/drivers/fsi/fsi-scom.c +++ b/drivers/fsi/fsi-scom.c @@ -24,6 +24,8 @@ #include #include +#include + #define FSI_ENGID_SCOM 0x5 /* SCOM engine register set */ @@ -41,14 +43,36 @@ /* Status register bits */ #define SCOM_STATUS_ERR_SUMMARY 0x80000000 #define SCOM_STATUS_PROTECTION 0x01000000 +#define SCOM_STATUS_PARITY 0x04000000 #define SCOM_STATUS_PIB_ABORT 0x00100000 #define SCOM_STATUS_PIB_RESP_MASK 0x00007000 #define SCOM_STATUS_PIB_RESP_SHIFT 12 #define SCOM_STATUS_ANY_ERR (SCOM_STATUS_ERR_SUMMARY | \ SCOM_STATUS_PROTECTION | \ + SCOM_STATUS_PARITY | \ SCOM_STATUS_PIB_ABORT | \ SCOM_STATUS_PIB_RESP_MASK) +/* SCOM address encodings */ +#define XSCOM_ADDR_IND_FLAG BIT_ULL(63) +#define XSCOM_ADDR_INF_FORM1 BIT_ULL(60) + +/* SCOM indirect stuff */ +#define XSCOM_ADDR_DIRECT_PART 0x7fffffffull +#define XSCOM_ADDR_INDIRECT_PART 0x000fffff00000000ull +#define XSCOM_DATA_IND_READ BIT_ULL(63) +#define XSCOM_DATA_IND_COMPLETE BIT_ULL(31) +#define XSCOM_DATA_IND_ERR_MASK 0x70000000ull +#define XSCOM_DATA_IND_ERR_SHIFT 28 +#define XSCOM_DATA_IND_DATA 0x0000ffffull +#define XSCOM_DATA_IND_FORM1_DATA 0x000fffffffffffffull +#define XSCOM_ADDR_FORM1_LOW 0x000ffffffffull +#define XSCOM_ADDR_FORM1_HI 0xfff00000000ull +#define XSCOM_ADDR_FORM1_HI_SHIFT 20 + +/* Retries */ +#define SCOM_MAX_RETRIES 100 /* Retries on busy */ +#define SCOM_MAX_IND_RETRIES 10 /* Retries indirect not ready */ struct scom_device { struct list_head link; @@ -56,7 +80,7 @@ struct scom_device { struct miscdevice mdev; struct mutex lock; char name[32]; - int idx; + int idx; }; #define to_scom_dev(x) container_of((x), struct scom_device, mdev) @@ -65,80 +89,304 @@ static struct list_head scom_devices; static DEFINE_IDA(scom_ida); -static int put_scom(struct scom_device *scom_dev, uint64_t value, - uint32_t addr) +static int __put_scom(struct scom_device *scom_dev, uint64_t value, + uint32_t addr, uint32_t *status) { - __be32 data; + __be32 data, raw_status; int rc; - mutex_lock(&scom_dev->lock); - data = cpu_to_be32((value >> 32) & 0xffffffff); rc = fsi_device_write(scom_dev->fsi_dev, SCOM_DATA0_REG, &data, sizeof(uint32_t)); if (rc) - goto bail; + return rc; data = cpu_to_be32(value & 0xffffffff); rc = fsi_device_write(scom_dev->fsi_dev, SCOM_DATA1_REG, &data, sizeof(uint32_t)); if (rc) - goto bail; + return rc; data = cpu_to_be32(SCOM_WRITE_CMD | addr); rc = fsi_device_write(scom_dev->fsi_dev, SCOM_CMD_REG, &data, sizeof(uint32_t)); - bail: - mutex_unlock(&scom_dev->lock); - return rc; + if (rc) + return rc; + rc = fsi_device_read(scom_dev->fsi_dev, SCOM_STATUS_REG, &raw_status, + sizeof(uint32_t)); + if (rc) + return rc; + *status = be32_to_cpu(raw_status); + + return 0; } -static int get_scom(struct scom_device *scom_dev, uint64_t *value, - uint32_t addr) +static int __get_scom(struct scom_device *scom_dev, uint64_t *value, + uint32_t addr, uint32_t *status) { - __be32 result, data; + __be32 data, raw_status; int rc; - mutex_lock(&scom_dev->lock); *value = 0ULL; data = cpu_to_be32(SCOM_READ_CMD | addr); rc = fsi_device_write(scom_dev->fsi_dev, SCOM_CMD_REG, &data, sizeof(uint32_t)); if (rc) - goto bail; + return rc; + rc = fsi_device_read(scom_dev->fsi_dev, SCOM_STATUS_REG, &raw_status, + sizeof(uint32_t)); + if (rc) + return rc; - rc = fsi_device_read(scom_dev->fsi_dev, SCOM_DATA0_REG, &result, + /* + * Read the data registers even on error, so we don't have + * to interpret the status register here. + */ + rc = fsi_device_read(scom_dev->fsi_dev, SCOM_DATA0_REG, &data, sizeof(uint32_t)); if (rc) - goto bail; - - *value |= (uint64_t)be32_to_cpu(result) << 32; - rc = fsi_device_read(scom_dev->fsi_dev, SCOM_DATA1_REG, &result, + return rc; + *value |= (uint64_t)be32_to_cpu(data) << 32; + rc = fsi_device_read(scom_dev->fsi_dev, SCOM_DATA1_REG, &data, sizeof(uint32_t)); if (rc) - goto bail; + return rc; + *value |= be32_to_cpu(data); + *status = be32_to_cpu(raw_status); + + return rc; +} + +static int put_indirect_scom_form0(struct scom_device *scom, uint64_t value, + uint64_t addr, uint32_t *status) +{ + uint64_t ind_data, ind_addr; + int rc, retries, err = 0; + + if (value & ~XSCOM_DATA_IND_DATA) + return -EINVAL; + + ind_addr = addr & XSCOM_ADDR_DIRECT_PART; + ind_data = (addr & XSCOM_ADDR_INDIRECT_PART) | value; + rc = __put_scom(scom, ind_data, ind_addr, status); + if (rc || (*status & SCOM_STATUS_ANY_ERR)) + return rc; + + for (retries = 0; retries < SCOM_MAX_IND_RETRIES; retries++) { + rc = __get_scom(scom, &ind_data, addr, status); + if (rc || (*status & SCOM_STATUS_ANY_ERR)) + return rc; + + err = (ind_data & XSCOM_DATA_IND_ERR_MASK) >> XSCOM_DATA_IND_ERR_SHIFT; + *status = err << SCOM_STATUS_PIB_RESP_SHIFT; + if ((ind_data & XSCOM_DATA_IND_COMPLETE) || (err != SCOM_PIB_BLOCKED)) + return 0; + + msleep(1); + } + return rc; +} + +static int put_indirect_scom_form1(struct scom_device *scom, uint64_t value, + uint64_t addr, uint32_t *status) +{ + uint64_t ind_data, ind_addr; + + if (value & ~XSCOM_DATA_IND_FORM1_DATA) + return -EINVAL; + + ind_addr = addr & XSCOM_ADDR_FORM1_LOW; + ind_data = value | (addr & XSCOM_ADDR_FORM1_HI) << XSCOM_ADDR_FORM1_HI_SHIFT; + return __put_scom(scom, ind_data, ind_addr, status); +} + +static int get_indirect_scom_form0(struct scom_device *scom, uint64_t *value, + uint64_t addr, uint32_t *status) +{ + uint64_t ind_data, ind_addr; + int rc, retries, err = 0; + + ind_addr = addr & XSCOM_ADDR_DIRECT_PART; + ind_data = (addr & XSCOM_ADDR_INDIRECT_PART) | XSCOM_DATA_IND_READ; + rc = __put_scom(scom, ind_data, ind_addr, status); + if (rc || (*status & SCOM_STATUS_ANY_ERR)) + return rc; + + for (retries = 0; retries < SCOM_MAX_IND_RETRIES; retries++) { + rc = __get_scom(scom, &ind_data, addr, status); + if (rc || (*status & SCOM_STATUS_ANY_ERR)) + return rc; + + err = (ind_data & XSCOM_DATA_IND_ERR_MASK) >> XSCOM_DATA_IND_ERR_SHIFT; + *status = err << SCOM_STATUS_PIB_RESP_SHIFT; + *value = ind_data & XSCOM_DATA_IND_DATA; + + if ((ind_data & XSCOM_DATA_IND_COMPLETE) || (err != SCOM_PIB_BLOCKED)) + return 0; + + msleep(1); + } + return rc; +} + +static int raw_put_scom(struct scom_device *scom, uint64_t value, + uint64_t addr, uint32_t *status) +{ + if (addr & XSCOM_ADDR_IND_FLAG) { + if (addr & XSCOM_ADDR_INF_FORM1) + return put_indirect_scom_form1(scom, value, addr, status); + else + return put_indirect_scom_form0(scom, value, addr, status); + } else + return __put_scom(scom, value, addr, status); +} + +static int raw_get_scom(struct scom_device *scom, uint64_t *value, + uint64_t addr, uint32_t *status) +{ + if (addr & XSCOM_ADDR_IND_FLAG) { + if (addr & XSCOM_ADDR_INF_FORM1) + return -ENXIO; + return get_indirect_scom_form0(scom, value, addr, status); + } else + return __get_scom(scom, value, addr, status); +} + +static int handle_fsi2pib_status(struct scom_device *scom, uint32_t status) +{ + uint32_t dummy = -1; + + if (status & SCOM_STATUS_PROTECTION) + return -EPERM; + if (status & SCOM_STATUS_PARITY) { + fsi_device_write(scom->fsi_dev, SCOM_FSI2PIB_RESET_REG, &dummy, + sizeof(uint32_t)); + return -EIO; + } + /* Return -EBUSY on PIB abort to force a retry */ + if (status & SCOM_STATUS_PIB_ABORT) + return -EBUSY; + if (status & SCOM_STATUS_ERR_SUMMARY) { + fsi_device_write(scom->fsi_dev, SCOM_FSI2PIB_RESET_REG, &dummy, + sizeof(uint32_t)); + return -EIO; + } + return 0; +} + +static int handle_pib_status(struct scom_device *scom, uint8_t status) +{ + uint32_t dummy = -1; + + if (status == SCOM_PIB_SUCCESS) + return 0; + if (status == SCOM_PIB_BLOCKED) + return -EBUSY; + + /* Reset the bridge */ + fsi_device_write(scom->fsi_dev, SCOM_FSI2PIB_RESET_REG, &dummy, + sizeof(uint32_t)); + + switch(status) { + case SCOM_PIB_OFFLINE: + return -ENODEV; + case SCOM_PIB_BAD_ADDR: + return -ENXIO; + case SCOM_PIB_TIMEOUT: + return -ETIMEDOUT; + case SCOM_PIB_PARTIAL: + case SCOM_PIB_CLK_ERR: + case SCOM_PIB_PARITY_ERR: + default: + return -EIO; + } +} - *value |= be32_to_cpu(result); - bail: - mutex_unlock(&scom_dev->lock); +static int put_scom(struct scom_device *scom, uint64_t value, + uint64_t addr) +{ + uint32_t status, dummy = -1; + int rc, retries; + + for (retries = 0; retries < SCOM_MAX_RETRIES; retries++) { + rc = raw_put_scom(scom, value, addr, &status); + if (rc) { + /* Try resetting the bridge if FSI fails */ + if (rc != -ENODEV && retries == 0) { + fsi_device_write(scom->fsi_dev, SCOM_FSI2PIB_RESET_REG, + &dummy, sizeof(uint32_t)); + rc = -EBUSY; + } else + return rc; + } else + rc = handle_fsi2pib_status(scom, status); + if (rc && rc != -EBUSY) + break; + if (rc == 0) { + rc = handle_pib_status(scom, + (status & SCOM_STATUS_PIB_RESP_MASK) + >> SCOM_STATUS_PIB_RESP_SHIFT); + if (rc && rc != -EBUSY) + break; + } + if (rc == 0) + break; + msleep(1); + } + return rc; +} + +static int get_scom(struct scom_device *scom, uint64_t *value, + uint64_t addr) +{ + uint32_t status, dummy = -1; + int rc, retries; + + for (retries = 0; retries < SCOM_MAX_RETRIES; retries++) { + rc = raw_get_scom(scom, value, addr, &status); + if (rc) { + /* Try resetting the bridge if FSI fails */ + if (rc != -ENODEV && retries == 0) { + fsi_device_write(scom->fsi_dev, SCOM_FSI2PIB_RESET_REG, + &dummy, sizeof(uint32_t)); + rc = -EBUSY; + } else + return rc; + } else + rc = handle_fsi2pib_status(scom, status); + if (rc && rc != -EBUSY) + break; + if (rc == 0) { + rc = handle_pib_status(scom, + (status & SCOM_STATUS_PIB_RESP_MASK) + >> SCOM_STATUS_PIB_RESP_SHIFT); + if (rc && rc != -EBUSY) + break; + } + if (rc == 0) + break; + msleep(1); + } return rc; } static ssize_t scom_read(struct file *filep, char __user *buf, size_t len, loff_t *offset) { - int rc; struct miscdevice *mdev = (struct miscdevice *)filep->private_data; struct scom_device *scom = to_scom_dev(mdev); struct device *dev = &scom->fsi_dev->dev; uint64_t val; + int rc; if (len != sizeof(uint64_t)) return -EINVAL; + mutex_lock(&scom->lock); rc = get_scom(scom, &val, *offset); + mutex_unlock(&scom->lock); if (rc) { dev_dbg(dev, "get_scom fail:%d\n", rc); return rc; @@ -169,7 +417,9 @@ static ssize_t scom_write(struct file *filep, const char __user *buf, return -EINVAL; } + mutex_lock(&scom->lock); rc = put_scom(scom, val, *offset); + mutex_unlock(&scom->lock); if (rc) { dev_dbg(dev, "put_scom failed with:%d\n", rc); return rc; @@ -193,11 +443,125 @@ static loff_t scom_llseek(struct file *file, loff_t offset, int whence) return offset; } +static void raw_convert_status(struct scom_access *acc, uint32_t status) +{ + acc->pib_status = (status & SCOM_STATUS_PIB_RESP_MASK) >> + SCOM_STATUS_PIB_RESP_SHIFT; + acc->intf_errors = 0; + + if (status & SCOM_STATUS_PROTECTION) + acc->intf_errors |= SCOM_INTF_ERR_PROTECTION; + else if (status & SCOM_STATUS_PARITY) + acc->intf_errors |= SCOM_INTF_ERR_PARITY; + else if (status & SCOM_STATUS_PIB_ABORT) + acc->intf_errors |= SCOM_INTF_ERR_ABORT; + else if (status & SCOM_STATUS_ERR_SUMMARY) + acc->intf_errors |= SCOM_INTF_ERR_UNKNOWN; +} + +static int scom_raw_read(struct scom_device *scom, void __user *argp) +{ + struct scom_access acc; + uint32_t status; + int rc; + + if (copy_from_user(&acc, argp, sizeof(struct scom_access))) + return -EFAULT; + + rc = raw_get_scom(scom, &acc.data, acc.addr, &status); + if (rc) + return rc; + raw_convert_status(&acc, status); + if (copy_to_user(argp, &acc, sizeof(struct scom_access))) + return -EFAULT; + return 0; +} + +static int scom_raw_write(struct scom_device *scom, void __user *argp) +{ + u64 prev_data, mask, data; + struct scom_access acc; + uint32_t status; + int rc; + + if (copy_from_user(&acc, argp, sizeof(struct scom_access))) + return -EFAULT; + + if (acc.mask) { + rc = raw_get_scom(scom, &prev_data, acc.addr, &status); + if (rc) + return rc; + if (status & SCOM_STATUS_ANY_ERR) + goto fail; + mask = acc.mask; + } else { + prev_data = mask = -1ull; + } + data = (prev_data & ~mask) | (acc.data & mask); + rc = raw_put_scom(scom, data, acc.addr, &status); + if (rc) + return rc; + fail: + raw_convert_status(&acc, status); + if (copy_to_user(argp, &acc, sizeof(struct scom_access))) + return -EFAULT; + return 0; +} + +static int scom_reset(struct scom_device *scom, void __user *argp) +{ + uint32_t flags, dummy = -1; + int rc = 0; + + if (get_user(flags, (__u32 __user *)argp)) + return -EFAULT; + if (flags & SCOM_RESET_PIB) + rc = fsi_device_write(scom->fsi_dev, SCOM_PIB_RESET_REG, &dummy, + sizeof(uint32_t)); + if (!rc && (flags & (SCOM_RESET_PIB | SCOM_RESET_INTF))) + rc = fsi_device_write(scom->fsi_dev, SCOM_FSI2PIB_RESET_REG, &dummy, + sizeof(uint32_t)); + return rc; +} + +static int scom_check(struct scom_device *scom, void __user *argp) +{ + /* Still need to find out how to get "protected" */ + return put_user(SCOM_CHECK_SUPPORTED, (__u32 __user *)argp); +} + +static long scom_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct miscdevice *mdev = file->private_data; + struct scom_device *scom = to_scom_dev(mdev); + void __user *argp = (void __user *)arg; + int rc = -ENOTTY; + + mutex_lock(&scom->lock); + switch(cmd) { + case FSI_SCOM_CHECK: + rc = scom_check(scom, argp); + break; + case FSI_SCOM_READ: + rc = scom_raw_read(scom, argp); + break; + case FSI_SCOM_WRITE: + rc = scom_raw_write(scom, argp); + break; + case FSI_SCOM_RESET: + rc = scom_reset(scom, argp); + break; + } + mutex_unlock(&scom->lock); + return rc; +} + static const struct file_operations scom_fops = { - .owner = THIS_MODULE, - .llseek = scom_llseek, - .read = scom_read, - .write = scom_write, + .owner = THIS_MODULE, + .llseek = scom_llseek, + .read = scom_read, + .write = scom_write, + .unlocked_ioctl = scom_ioctl, }; static int scom_probe(struct device *dev) diff --git a/include/uapi/linux/fsi.h b/include/uapi/linux/fsi.h new file mode 100644 index 000000000000..da577ecd90e7 --- /dev/null +++ b/include/uapi/linux/fsi.h @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */ +#ifndef _UAPI_LINUX_FSI_H +#define _UAPI_LINUX_FSI_H + +#include +#include + +/* + * /dev/scom "raw" ioctl interface + * + * The driver supports a high level "read/write" interface which + * handles retries and converts the status to Linux error codes, + * however low level tools an debugger need to access the "raw" + * HW status information and interpret it themselves, so this + * ioctl interface is also provided for their use case. + */ + +/* Structure for SCOM read/write */ +struct scom_access { + __u64 addr; /* SCOM address, supports indirect */ + __u64 data; /* SCOM data (in for write, out for read) */ + __u64 mask; /* Data mask for writes */ + __u32 intf_errors; /* Interface error flags */ +#define SCOM_INTF_ERR_PARITY 0x00000001 /* Parity error */ +#define SCOM_INTF_ERR_PROTECTION 0x00000002 /* Blocked by secure boot */ +#define SCOM_INTF_ERR_ABORT 0x00000004 /* PIB reset during access */ +#define SCOM_INTF_ERR_UNKNOWN 0x80000000 /* Unknown error */ + /* + * Note: Any other bit set in intf_errors need to be considered as an + * error. Future implementations may define new error conditions. The + * pib_status below is only valid if intf_errors is 0. + */ + __u8 pib_status; /* 3-bit PIB status */ +#define SCOM_PIB_SUCCESS 0 /* Access successful */ +#define SCOM_PIB_BLOCKED 1 /* PIB blocked, pls retry */ +#define SCOM_PIB_OFFLINE 2 /* Chiplet offline */ +#define SCOM_PIB_PARTIAL 3 /* Partial good */ +#define SCOM_PIB_BAD_ADDR 4 /* Invalid address */ +#define SCOM_PIB_CLK_ERR 5 /* Clock error */ +#define SCOM_PIB_PARITY_ERR 6 /* Parity error on the PIB bus */ +#define SCOM_PIB_TIMEOUT 7 /* Bus timeout */ + __u8 pad; +}; + +/* Flags for SCOM check */ +#define SCOM_CHECK_SUPPORTED 0x00000001 /* Interface supported */ +#define SCOM_CHECK_PROTECTED 0x00000002 /* Interface blocked by secure boot */ + +/* Flags for SCOM reset */ +#define SCOM_RESET_INTF 0x00000001 /* Reset interface */ +#define SCOM_RESET_PIB 0x00000002 /* Reset PIB */ + +#define FSI_SCOM_CHECK _IOR('s', 0x00, __u32) +#define FSI_SCOM_READ _IOWR('s', 0x01, struct scom_access) +#define FSI_SCOM_WRITE _IOWR('s', 0x02, struct scom_access) +#define FSI_SCOM_RESET _IOW('s', 0x03, __u32) + +#endif /* _UAPI_LINUX_FSI_H */ -- cgit v1.2.3 From 2b6a440351436d792b1960822da4b7d6e673f568 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Fri, 1 Jun 2018 10:22:52 +0200 Subject: gnss: add GNSS receiver subsystem Add a new subsystem for GNSS (e.g. GPS) receivers. While GNSS receivers are typically accessed using a UART interface they often also support other I/O interfaces such as I2C, SPI and USB, while yet other devices use iomem or even some form of remote-processor messaging (rpmsg). The new GNSS subsystem abstracts the underlying interface and provides a new "gnss" class type, which exposes a character-device interface (e.g. /dev/gnss0) to user space. This allows GNSS receivers to have a representation in the Linux device model, something which is important not least for power management purposes. Note that the character-device interface provides raw access to whatever protocol the receiver is (currently) using, such as NMEA 0183, UBX or SiRF Binary. These protocols are expected to be continued to be handled by user space for the time being, even if some hybrid solutions are also conceivable (e.g. to have kernel drivers issue management commands). This will still allow for better platform integration by allowing GNSS devices and their resources (e.g. regulators and enable-gpios) to be described by firmware and managed by kernel drivers rather than platform-specific scripts and services. While the current interface is kept minimal, it could be extended using IOCTLs, sysfs or uevents as needs and proper abstraction levels are identified and determined (e.g. for device and feature identification). Signed-off-by: Johan Hovold Signed-off-by: Greg Kroah-Hartman --- MAINTAINERS | 6 + drivers/Kconfig | 2 + drivers/Makefile | 1 + drivers/gnss/Kconfig | 11 ++ drivers/gnss/Makefile | 7 + drivers/gnss/core.c | 371 ++++++++++++++++++++++++++++++++++++++++++++++++++ include/linux/gnss.h | 66 +++++++++ 7 files changed, 464 insertions(+) create mode 100644 drivers/gnss/Kconfig create mode 100644 drivers/gnss/Makefile create mode 100644 drivers/gnss/core.c create mode 100644 include/linux/gnss.h diff --git a/MAINTAINERS b/MAINTAINERS index 6cfd16790add..436e6dcabf34 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6038,6 +6038,12 @@ F: Documentation/isdn/README.gigaset F: drivers/isdn/gigaset/ F: include/uapi/linux/gigaset_dev.h +GNSS SUBSYSTEM +M: Johan Hovold +S: Maintained +F: drivers/gnss/ +F: include/linux/gnss.h + GO7007 MPEG CODEC M: Hans Verkuil L: linux-media@vger.kernel.org diff --git a/drivers/Kconfig b/drivers/Kconfig index 95b9ccc08165..ab4d43923c4d 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -9,6 +9,8 @@ source "drivers/bus/Kconfig" source "drivers/connector/Kconfig" +source "drivers/gnss/Kconfig" + source "drivers/mtd/Kconfig" source "drivers/of/Kconfig" diff --git a/drivers/Makefile b/drivers/Makefile index 24cd47014657..cc9a7c5f7d2c 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -185,3 +185,4 @@ obj-$(CONFIG_TEE) += tee/ obj-$(CONFIG_MULTIPLEXER) += mux/ obj-$(CONFIG_UNISYS_VISORBUS) += visorbus/ obj-$(CONFIG_SIOX) += siox/ +obj-$(CONFIG_GNSS) += gnss/ diff --git a/drivers/gnss/Kconfig b/drivers/gnss/Kconfig new file mode 100644 index 000000000000..103fcc70992e --- /dev/null +++ b/drivers/gnss/Kconfig @@ -0,0 +1,11 @@ +# +# GNSS receiver configuration +# + +menuconfig GNSS + tristate "GNSS receiver support" + ---help--- + Say Y here if you have a GNSS receiver (e.g. a GPS receiver). + + To compile this driver as a module, choose M here: the module will + be called gnss. diff --git a/drivers/gnss/Makefile b/drivers/gnss/Makefile new file mode 100644 index 000000000000..1f7a7baab1d9 --- /dev/null +++ b/drivers/gnss/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the GNSS subsystem. +# + +obj-$(CONFIG_GNSS) += gnss.o +gnss-y := core.o diff --git a/drivers/gnss/core.c b/drivers/gnss/core.c new file mode 100644 index 000000000000..307894ca2725 --- /dev/null +++ b/drivers/gnss/core.c @@ -0,0 +1,371 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * GNSS receiver core + * + * Copyright (C) 2018 Johan Hovold + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define GNSS_FLAG_HAS_WRITE_RAW BIT(0) + +#define GNSS_MINORS 16 + +static DEFINE_IDA(gnss_minors); +static dev_t gnss_first; + +/* FIFO size must be a power of two */ +#define GNSS_READ_FIFO_SIZE 4096 +#define GNSS_WRITE_BUF_SIZE 1024 + +#define to_gnss_device(d) container_of((d), struct gnss_device, dev) + +static int gnss_open(struct inode *inode, struct file *file) +{ + struct gnss_device *gdev; + int ret = 0; + + gdev = container_of(inode->i_cdev, struct gnss_device, cdev); + + get_device(&gdev->dev); + + nonseekable_open(inode, file); + file->private_data = gdev; + + down_write(&gdev->rwsem); + if (gdev->disconnected) { + ret = -ENODEV; + goto unlock; + } + + if (gdev->count++ == 0) { + ret = gdev->ops->open(gdev); + if (ret) + gdev->count--; + } +unlock: + up_write(&gdev->rwsem); + + if (ret) + put_device(&gdev->dev); + + return ret; +} + +static int gnss_release(struct inode *inode, struct file *file) +{ + struct gnss_device *gdev = file->private_data; + + down_write(&gdev->rwsem); + if (gdev->disconnected) + goto unlock; + + if (--gdev->count == 0) { + gdev->ops->close(gdev); + kfifo_reset(&gdev->read_fifo); + } +unlock: + up_write(&gdev->rwsem); + + put_device(&gdev->dev); + + return 0; +} + +static ssize_t gnss_read(struct file *file, char __user *buf, + size_t count, loff_t *pos) +{ + struct gnss_device *gdev = file->private_data; + unsigned int copied; + int ret; + + mutex_lock(&gdev->read_mutex); + while (kfifo_is_empty(&gdev->read_fifo)) { + mutex_unlock(&gdev->read_mutex); + + if (gdev->disconnected) + return 0; + + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + + ret = wait_event_interruptible(gdev->read_queue, + gdev->disconnected || + !kfifo_is_empty(&gdev->read_fifo)); + if (ret) + return -ERESTARTSYS; + + mutex_lock(&gdev->read_mutex); + } + + ret = kfifo_to_user(&gdev->read_fifo, buf, count, &copied); + if (ret == 0) + ret = copied; + + mutex_unlock(&gdev->read_mutex); + + return ret; +} + +static ssize_t gnss_write(struct file *file, const char __user *buf, + size_t count, loff_t *pos) +{ + struct gnss_device *gdev = file->private_data; + size_t written = 0; + int ret; + + if (gdev->disconnected) + return -EIO; + + if (!count) + return 0; + + if (!(gdev->flags & GNSS_FLAG_HAS_WRITE_RAW)) + return -EIO; + + /* Ignoring O_NONBLOCK, write_raw() is synchronous. */ + + ret = mutex_lock_interruptible(&gdev->write_mutex); + if (ret) + return -ERESTARTSYS; + + for (;;) { + size_t n = count - written; + + if (n > GNSS_WRITE_BUF_SIZE) + n = GNSS_WRITE_BUF_SIZE; + + if (copy_from_user(gdev->write_buf, buf, n)) { + ret = -EFAULT; + goto out_unlock; + } + + /* + * Assumes write_raw can always accept GNSS_WRITE_BUF_SIZE + * bytes. + * + * FIXME: revisit + */ + down_read(&gdev->rwsem); + if (!gdev->disconnected) + ret = gdev->ops->write_raw(gdev, gdev->write_buf, n); + else + ret = -EIO; + up_read(&gdev->rwsem); + + if (ret < 0) + break; + + written += ret; + buf += ret; + + if (written == count) + break; + } + + if (written) + ret = written; +out_unlock: + mutex_unlock(&gdev->write_mutex); + + return ret; +} + +static __poll_t gnss_poll(struct file *file, poll_table *wait) +{ + struct gnss_device *gdev = file->private_data; + __poll_t mask = 0; + + poll_wait(file, &gdev->read_queue, wait); + + if (!kfifo_is_empty(&gdev->read_fifo)) + mask |= EPOLLIN | EPOLLRDNORM; + if (gdev->disconnected) + mask |= EPOLLHUP; + + return mask; +} + +static const struct file_operations gnss_fops = { + .owner = THIS_MODULE, + .open = gnss_open, + .release = gnss_release, + .read = gnss_read, + .write = gnss_write, + .poll = gnss_poll, + .llseek = no_llseek, +}; + +static struct class *gnss_class; + +static void gnss_device_release(struct device *dev) +{ + struct gnss_device *gdev = to_gnss_device(dev); + + kfree(gdev->write_buf); + kfifo_free(&gdev->read_fifo); + ida_simple_remove(&gnss_minors, gdev->id); + kfree(gdev); +} + +struct gnss_device *gnss_allocate_device(struct device *parent) +{ + struct gnss_device *gdev; + struct device *dev; + int id; + int ret; + + gdev = kzalloc(sizeof(*gdev), GFP_KERNEL); + if (!gdev) + return NULL; + + id = ida_simple_get(&gnss_minors, 0, GNSS_MINORS, GFP_KERNEL); + if (id < 0) { + kfree(gdev); + return ERR_PTR(id); + } + + gdev->id = id; + + dev = &gdev->dev; + device_initialize(dev); + dev->devt = gnss_first + id; + dev->class = gnss_class; + dev->parent = parent; + dev->release = gnss_device_release; + dev_set_drvdata(dev, gdev); + dev_set_name(dev, "gnss%d", id); + + init_rwsem(&gdev->rwsem); + mutex_init(&gdev->read_mutex); + mutex_init(&gdev->write_mutex); + init_waitqueue_head(&gdev->read_queue); + + ret = kfifo_alloc(&gdev->read_fifo, GNSS_READ_FIFO_SIZE, GFP_KERNEL); + if (ret) + goto err_put_device; + + gdev->write_buf = kzalloc(GNSS_WRITE_BUF_SIZE, GFP_KERNEL); + if (!gdev->write_buf) + goto err_put_device; + + cdev_init(&gdev->cdev, &gnss_fops); + gdev->cdev.owner = THIS_MODULE; + + return gdev; + +err_put_device: + put_device(dev); + + return ERR_PTR(-ENOMEM); +} +EXPORT_SYMBOL_GPL(gnss_allocate_device); + +void gnss_put_device(struct gnss_device *gdev) +{ + put_device(&gdev->dev); +} +EXPORT_SYMBOL_GPL(gnss_put_device); + +int gnss_register_device(struct gnss_device *gdev) +{ + int ret; + + /* Set a flag which can be accessed without holding the rwsem. */ + if (gdev->ops->write_raw != NULL) + gdev->flags |= GNSS_FLAG_HAS_WRITE_RAW; + + ret = cdev_device_add(&gdev->cdev, &gdev->dev); + if (ret) { + dev_err(&gdev->dev, "failed to add device: %d\n", ret); + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(gnss_register_device); + +void gnss_deregister_device(struct gnss_device *gdev) +{ + down_write(&gdev->rwsem); + gdev->disconnected = true; + if (gdev->count) { + wake_up_interruptible(&gdev->read_queue); + gdev->ops->close(gdev); + } + up_write(&gdev->rwsem); + + cdev_device_del(&gdev->cdev, &gdev->dev); +} +EXPORT_SYMBOL_GPL(gnss_deregister_device); + +/* + * Caller guarantees serialisation. + * + * Must not be called for a closed device. + */ +int gnss_insert_raw(struct gnss_device *gdev, const unsigned char *buf, + size_t count) +{ + int ret; + + ret = kfifo_in(&gdev->read_fifo, buf, count); + + wake_up_interruptible(&gdev->read_queue); + + return ret; +} +EXPORT_SYMBOL_GPL(gnss_insert_raw); + +static int __init gnss_module_init(void) +{ + int ret; + + ret = alloc_chrdev_region(&gnss_first, 0, GNSS_MINORS, "gnss"); + if (ret < 0) { + pr_err("failed to allocate device numbers: %d\n", ret); + return ret; + } + + gnss_class = class_create(THIS_MODULE, "gnss"); + if (IS_ERR(gnss_class)) { + ret = PTR_ERR(gnss_class); + pr_err("failed to create class: %d\n", ret); + goto err_unregister_chrdev; + } + + pr_info("GNSS driver registered with major %d\n", MAJOR(gnss_first)); + + return 0; + +err_unregister_chrdev: + unregister_chrdev_region(gnss_first, GNSS_MINORS); + + return ret; +} +module_init(gnss_module_init); + +static void __exit gnss_module_exit(void) +{ + class_destroy(gnss_class); + unregister_chrdev_region(gnss_first, GNSS_MINORS); + ida_destroy(&gnss_minors); +} +module_exit(gnss_module_exit); + +MODULE_AUTHOR("Johan Hovold "); +MODULE_DESCRIPTION("GNSS receiver core"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/gnss.h b/include/linux/gnss.h new file mode 100644 index 000000000000..e26aeac1e0e2 --- /dev/null +++ b/include/linux/gnss.h @@ -0,0 +1,66 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * GNSS receiver support + * + * Copyright (C) 2018 Johan Hovold + */ + +#ifndef _LINUX_GNSS_H +#define _LINUX_GNSS_H + +#include +#include +#include +#include +#include +#include +#include + +struct gnss_device; + +struct gnss_operations { + int (*open)(struct gnss_device *gdev); + void (*close)(struct gnss_device *gdev); + int (*write_raw)(struct gnss_device *gdev, const unsigned char *buf, + size_t count); +}; + +struct gnss_device { + struct device dev; + struct cdev cdev; + int id; + + unsigned long flags; + + struct rw_semaphore rwsem; + const struct gnss_operations *ops; + unsigned int count; + unsigned int disconnected:1; + + struct mutex read_mutex; + struct kfifo read_fifo; + wait_queue_head_t read_queue; + + struct mutex write_mutex; + char *write_buf; +}; + +struct gnss_device *gnss_allocate_device(struct device *parent); +void gnss_put_device(struct gnss_device *gdev); +int gnss_register_device(struct gnss_device *gdev); +void gnss_deregister_device(struct gnss_device *gdev); + +int gnss_insert_raw(struct gnss_device *gdev, const unsigned char *buf, + size_t count); + +static inline void gnss_set_drvdata(struct gnss_device *gdev, void *data) +{ + dev_set_drvdata(&gdev->dev, data); +} + +static inline void *gnss_get_drvdata(struct gnss_device *gdev) +{ + return dev_get_drvdata(&gdev->dev); +} + +#endif /* _LINUX_GNSS_H */ -- cgit v1.2.3 From 98ddec80fdf1c3e6c594fe633e6fb2a8a0d699dd Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Fri, 1 Jun 2018 10:22:53 +0200 Subject: dt-bindings: add generic gnss binding Describe generic properties for GNSS receivers. Reviewed-by: Rob Herring Signed-off-by: Johan Hovold Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/gnss/gnss.txt | 36 +++++++++++++++++++++++++ MAINTAINERS | 1 + 2 files changed, 37 insertions(+) create mode 100644 Documentation/devicetree/bindings/gnss/gnss.txt diff --git a/Documentation/devicetree/bindings/gnss/gnss.txt b/Documentation/devicetree/bindings/gnss/gnss.txt new file mode 100644 index 000000000000..f1e4a2ff47c5 --- /dev/null +++ b/Documentation/devicetree/bindings/gnss/gnss.txt @@ -0,0 +1,36 @@ +GNSS Receiver DT binding + +This documents the binding structure and common properties for GNSS receiver +devices. + +A GNSS receiver node is a node named "gnss" and typically resides on a serial +bus (e.g. UART, I2C or SPI). + +Please refer to the following documents for generic properties: + + Documentation/devicetree/bindings/serial/slave-device.txt + Documentation/devicetree/bindings/spi/spi-bus.txt + +Required properties: + +- compatible : A string reflecting the vendor and specific device the node + represents + +Optional properties: +- enable-gpios : GPIO used to enable the device +- timepulse-gpios : Time pulse GPIO + +Example: + +serial@1234 { + compatible = "ns16550a"; + + gnss { + compatible = "u-blox,neo-8"; + + vcc-supply = <&gnss_reg>; + timepulse-gpios = <&gpio0 16 GPIO_ACTIVE_HIGH>; + + current-speed = <4800>; + }; +}; diff --git a/MAINTAINERS b/MAINTAINERS index 436e6dcabf34..f980c6186094 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6041,6 +6041,7 @@ F: include/uapi/linux/gigaset_dev.h GNSS SUBSYSTEM M: Johan Hovold S: Maintained +F: Documentation/devicetree/bindings/gnss/ F: drivers/gnss/ F: include/linux/gnss.h -- cgit v1.2.3 From 37768b054f2074f40de3cacd492baed482f5d9da Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Fri, 1 Jun 2018 10:22:54 +0200 Subject: gnss: add generic serial driver Add a generic serial GNSS driver (library) which provides a common implementation for the gnss interface and power management (runtime and system suspend). This allows GNSS drivers for specific chip to be implemented by simply providing a set_power() callback to handle three states: ACTIVE, STANDBY and OFF. Signed-off-by: Johan Hovold Signed-off-by: Greg Kroah-Hartman --- drivers/gnss/Kconfig | 7 ++ drivers/gnss/Makefile | 3 + drivers/gnss/serial.c | 275 ++++++++++++++++++++++++++++++++++++++++++++++++++ drivers/gnss/serial.h | 47 +++++++++ 4 files changed, 332 insertions(+) create mode 100644 drivers/gnss/serial.c create mode 100644 drivers/gnss/serial.h diff --git a/drivers/gnss/Kconfig b/drivers/gnss/Kconfig index 103fcc70992e..f8ee54f99a8d 100644 --- a/drivers/gnss/Kconfig +++ b/drivers/gnss/Kconfig @@ -9,3 +9,10 @@ menuconfig GNSS To compile this driver as a module, choose M here: the module will be called gnss. + +if GNSS + +config GNSS_SERIAL + tristate + +endif # GNSS diff --git a/drivers/gnss/Makefile b/drivers/gnss/Makefile index 1f7a7baab1d9..171aba71684d 100644 --- a/drivers/gnss/Makefile +++ b/drivers/gnss/Makefile @@ -5,3 +5,6 @@ obj-$(CONFIG_GNSS) += gnss.o gnss-y := core.o + +obj-$(CONFIG_GNSS_SERIAL) += gnss-serial.o +gnss-serial-y := serial.o diff --git a/drivers/gnss/serial.c b/drivers/gnss/serial.c new file mode 100644 index 000000000000..b01ba4438501 --- /dev/null +++ b/drivers/gnss/serial.c @@ -0,0 +1,275 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Generic serial GNSS receiver driver + * + * Copyright (C) 2018 Johan Hovold + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "serial.h" + +static int gnss_serial_open(struct gnss_device *gdev) +{ + struct gnss_serial *gserial = gnss_get_drvdata(gdev); + struct serdev_device *serdev = gserial->serdev; + int ret; + + ret = serdev_device_open(serdev); + if (ret) + return ret; + + serdev_device_set_baudrate(serdev, gserial->speed); + serdev_device_set_flow_control(serdev, false); + + ret = pm_runtime_get_sync(&serdev->dev); + if (ret < 0) { + pm_runtime_put_noidle(&serdev->dev); + goto err_close; + } + + return 0; + +err_close: + serdev_device_close(serdev); + + return ret; +} + +static void gnss_serial_close(struct gnss_device *gdev) +{ + struct gnss_serial *gserial = gnss_get_drvdata(gdev); + struct serdev_device *serdev = gserial->serdev; + + serdev_device_close(serdev); + + pm_runtime_put(&serdev->dev); +} + +static int gnss_serial_write_raw(struct gnss_device *gdev, + const unsigned char *buf, size_t count) +{ + struct gnss_serial *gserial = gnss_get_drvdata(gdev); + struct serdev_device *serdev = gserial->serdev; + int ret; + + /* write is only buffered synchronously */ + ret = serdev_device_write(serdev, buf, count, 0); + if (ret < 0) + return ret; + + /* FIXME: determine if interrupted? */ + serdev_device_wait_until_sent(serdev, 0); + + return count; +} + +static const struct gnss_operations gnss_serial_gnss_ops = { + .open = gnss_serial_open, + .close = gnss_serial_close, + .write_raw = gnss_serial_write_raw, +}; + +static int gnss_serial_receive_buf(struct serdev_device *serdev, + const unsigned char *buf, size_t count) +{ + struct gnss_serial *gserial = serdev_device_get_drvdata(serdev); + struct gnss_device *gdev = gserial->gdev; + + return gnss_insert_raw(gdev, buf, count); +} + +static const struct serdev_device_ops gnss_serial_serdev_ops = { + .receive_buf = gnss_serial_receive_buf, + .write_wakeup = serdev_device_write_wakeup, +}; + +static int gnss_serial_set_power(struct gnss_serial *gserial, + enum gnss_serial_pm_state state) +{ + if (!gserial->ops || !gserial->ops->set_power) + return 0; + + return gserial->ops->set_power(gserial, state); +} + +/* + * FIXME: need to provide subdriver defaults or separate dt parsing from + * allocation. + */ +static int gnss_serial_parse_dt(struct serdev_device *serdev) +{ + struct gnss_serial *gserial = serdev_device_get_drvdata(serdev); + struct device_node *node = serdev->dev.of_node; + u32 speed = 4800; + + of_property_read_u32(node, "current-speed", &speed); + + gserial->speed = speed; + + return 0; +} + +struct gnss_serial *gnss_serial_allocate(struct serdev_device *serdev, + size_t data_size) +{ + struct gnss_serial *gserial; + struct gnss_device *gdev; + int ret; + + gserial = kzalloc(sizeof(*gserial) + data_size, GFP_KERNEL); + if (!gserial) + return ERR_PTR(-ENOMEM); + + gdev = gnss_allocate_device(&serdev->dev); + if (!gdev) { + ret = -ENOMEM; + goto err_free_gserial; + } + + gdev->ops = &gnss_serial_gnss_ops; + gnss_set_drvdata(gdev, gserial); + + gserial->serdev = serdev; + gserial->gdev = gdev; + + serdev_device_set_drvdata(serdev, gserial); + serdev_device_set_client_ops(serdev, &gnss_serial_serdev_ops); + + ret = gnss_serial_parse_dt(serdev); + if (ret) + goto err_put_device; + + return gserial; + +err_put_device: + gnss_put_device(gserial->gdev); +err_free_gserial: + kfree(gserial); + + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(gnss_serial_allocate); + +void gnss_serial_free(struct gnss_serial *gserial) +{ + gnss_put_device(gserial->gdev); + kfree(gserial); +}; +EXPORT_SYMBOL_GPL(gnss_serial_free); + +int gnss_serial_register(struct gnss_serial *gserial) +{ + struct serdev_device *serdev = gserial->serdev; + int ret; + + if (IS_ENABLED(CONFIG_PM)) { + pm_runtime_enable(&serdev->dev); + } else { + ret = gnss_serial_set_power(gserial, GNSS_SERIAL_ACTIVE); + if (ret < 0) + return ret; + } + + ret = gnss_register_device(gserial->gdev); + if (ret) + goto err_disable_rpm; + + return 0; + +err_disable_rpm: + if (IS_ENABLED(CONFIG_PM)) + pm_runtime_disable(&serdev->dev); + else + gnss_serial_set_power(gserial, GNSS_SERIAL_OFF); + + return ret; +} +EXPORT_SYMBOL_GPL(gnss_serial_register); + +void gnss_serial_deregister(struct gnss_serial *gserial) +{ + struct serdev_device *serdev = gserial->serdev; + + gnss_deregister_device(gserial->gdev); + + if (IS_ENABLED(CONFIG_PM)) + pm_runtime_disable(&serdev->dev); + else + gnss_serial_set_power(gserial, GNSS_SERIAL_OFF); +} +EXPORT_SYMBOL_GPL(gnss_serial_deregister); + +#ifdef CONFIG_PM +static int gnss_serial_runtime_suspend(struct device *dev) +{ + struct gnss_serial *gserial = dev_get_drvdata(dev); + + return gnss_serial_set_power(gserial, GNSS_SERIAL_STANDBY); +} + +static int gnss_serial_runtime_resume(struct device *dev) +{ + struct gnss_serial *gserial = dev_get_drvdata(dev); + + return gnss_serial_set_power(gserial, GNSS_SERIAL_ACTIVE); +} +#endif /* CONFIG_PM */ + +static int gnss_serial_prepare(struct device *dev) +{ + if (pm_runtime_suspended(dev)) + return 1; + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int gnss_serial_suspend(struct device *dev) +{ + struct gnss_serial *gserial = dev_get_drvdata(dev); + int ret = 0; + + /* + * FIXME: serdev currently lacks support for managing the underlying + * device's wakeup settings. A workaround would be to close the serdev + * device here if it is open. + */ + + if (!pm_runtime_suspended(dev)) + ret = gnss_serial_set_power(gserial, GNSS_SERIAL_STANDBY); + + return ret; +} + +static int gnss_serial_resume(struct device *dev) +{ + struct gnss_serial *gserial = dev_get_drvdata(dev); + int ret = 0; + + if (!pm_runtime_suspended(dev)) + ret = gnss_serial_set_power(gserial, GNSS_SERIAL_ACTIVE); + + return ret; +} +#endif /* CONFIG_PM_SLEEP */ + +const struct dev_pm_ops gnss_serial_pm_ops = { + .prepare = gnss_serial_prepare, + SET_SYSTEM_SLEEP_PM_OPS(gnss_serial_suspend, gnss_serial_resume) + SET_RUNTIME_PM_OPS(gnss_serial_runtime_suspend, gnss_serial_runtime_resume, NULL) +}; +EXPORT_SYMBOL_GPL(gnss_serial_pm_ops); + +MODULE_AUTHOR("Johan Hovold "); +MODULE_DESCRIPTION("Generic serial GNSS receiver driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gnss/serial.h b/drivers/gnss/serial.h new file mode 100644 index 000000000000..980ffdc86c2a --- /dev/null +++ b/drivers/gnss/serial.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Generic serial GNSS receiver driver + * + * Copyright (C) 2018 Johan Hovold + */ + +#ifndef _LINUX_GNSS_SERIAL_H +#define _LINUX_GNSS_SERIAL_H + +#include +#include + +struct gnss_serial { + struct serdev_device *serdev; + struct gnss_device *gdev; + speed_t speed; + const struct gnss_serial_ops *ops; + unsigned long drvdata[0]; +}; + +enum gnss_serial_pm_state { + GNSS_SERIAL_OFF, + GNSS_SERIAL_ACTIVE, + GNSS_SERIAL_STANDBY, +}; + +struct gnss_serial_ops { + int (*set_power)(struct gnss_serial *gserial, + enum gnss_serial_pm_state state); +}; + +extern const struct dev_pm_ops gnss_serial_pm_ops; + +struct gnss_serial *gnss_serial_allocate(struct serdev_device *gserial, + size_t data_size); +void gnss_serial_free(struct gnss_serial *gserial); + +int gnss_serial_register(struct gnss_serial *gserial); +void gnss_serial_deregister(struct gnss_serial *gserial); + +static inline void *gnss_serial_get_drvdata(struct gnss_serial *gserial) +{ + return gserial->drvdata; +} + +#endif /* _LINUX_GNSS_SERIAL_H */ -- cgit v1.2.3 From cf408372e5f583c5289319f597a2816f282b572b Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Fri, 1 Jun 2018 10:22:55 +0200 Subject: dt-bindings: gnss: add u-blox binding Add binding for u-blox GNSS receivers. Note that the u-blox product names encodes form factor (e.g. "neo"), chipset (e.g. "8") and variant (e.g. "q"), but that only formfactor and chipset is used for the compatible strings (for now). Signed-off-by: Johan Hovold Reviewed-by: Rob Herring Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/gnss/u-blox.txt | 44 ++++++++++++++++++++++ .../devicetree/bindings/vendor-prefixes.txt | 1 + 2 files changed, 45 insertions(+) create mode 100644 Documentation/devicetree/bindings/gnss/u-blox.txt diff --git a/Documentation/devicetree/bindings/gnss/u-blox.txt b/Documentation/devicetree/bindings/gnss/u-blox.txt new file mode 100644 index 000000000000..e475659cb85f --- /dev/null +++ b/Documentation/devicetree/bindings/gnss/u-blox.txt @@ -0,0 +1,44 @@ +u-blox GNSS Receiver DT binding + +The u-blox GNSS receivers can use UART, DDC (I2C), SPI and USB interfaces. + +Please see Documentation/devicetree/bindings/gnss/gnss.txt for generic +properties. + +Required properties: + +- compatible : Must be one of + + "u-blox,neo-8" + "u-blox,neo-m8" + +- vcc-supply : Main voltage regulator + +Required properties (DDC): +- reg : DDC (I2C) slave address + +Required properties (SPI): +- reg : SPI chip select address + +Required properties (USB): +- reg : Number of the USB hub port or the USB host-controller port + to which this device is attached + +Optional properties: + +- timepulse-gpios : Time pulse GPIO +- u-blox,extint-gpios : GPIO connected to the "external interrupt" input pin +- v-bckp-supply : Backup voltage regulator + +Example: + +serial@1234 { + compatible = "ns16550a"; + + gnss { + compatible = "u-blox,neo-8"; + + v-bckp-supply = <&gnss_v_bckp_reg>; + vcc-supply = <&gnss_vcc_reg>; + }; +}; diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt index 7cad066191ee..4f61ca1b38e4 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.txt +++ b/Documentation/devicetree/bindings/vendor-prefixes.txt @@ -384,6 +384,7 @@ tronsmart Tronsmart truly Truly Semiconductors Limited tsd Theobroma Systems Design und Consulting GmbH tyan Tyan Computer Corporation +u-blox u-blox ucrobotics uCRobotics ubnt Ubiquiti Networks udoo Udoo -- cgit v1.2.3 From 1ad69f10e3a58db60210f2869d5afddb40714507 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Fri, 1 Jun 2018 10:22:56 +0200 Subject: gnss: add driver for u-blox receivers Add driver for serial-connected u-blox GNSS receivers. Note that the driver uses the generic GNSS serial implementation and therefore essentially only manages power abstracted into three power states: ACTIVE, STANDBY, and OFF. For u-blox receivers with a main supply and no enable-gpios, this simply means that the main supply is disabled in STANDBY and OFF (the optional backup supply is kept enabled while the driver is bound). Note that timepulse-support is not yet implemented. Signed-off-by: Johan Hovold Signed-off-by: Greg Kroah-Hartman --- drivers/gnss/Kconfig | 13 +++++ drivers/gnss/Makefile | 3 + drivers/gnss/ubx.c | 151 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 167 insertions(+) create mode 100644 drivers/gnss/ubx.c diff --git a/drivers/gnss/Kconfig b/drivers/gnss/Kconfig index f8ee54f99a8d..784b8c0367d9 100644 --- a/drivers/gnss/Kconfig +++ b/drivers/gnss/Kconfig @@ -15,4 +15,17 @@ if GNSS config GNSS_SERIAL tristate +config GNSS_UBX_SERIAL + tristate "u-blox GNSS receiver support" + depends on SERIAL_DEV_BUS + select GNSS_SERIAL + ---help--- + Say Y here if you have a u-blox GNSS receiver which uses a serial + interface. + + To compile this driver as a module, choose M here: the module will + be called gnss-ubx. + + If unsure, say N. + endif # GNSS diff --git a/drivers/gnss/Makefile b/drivers/gnss/Makefile index 171aba71684d..d9295b20b7bc 100644 --- a/drivers/gnss/Makefile +++ b/drivers/gnss/Makefile @@ -8,3 +8,6 @@ gnss-y := core.o obj-$(CONFIG_GNSS_SERIAL) += gnss-serial.o gnss-serial-y := serial.o + +obj-$(CONFIG_GNSS_UBX_SERIAL) += gnss-ubx.o +gnss-ubx-y := ubx.o diff --git a/drivers/gnss/ubx.c b/drivers/gnss/ubx.c new file mode 100644 index 000000000000..ecddfb362a6f --- /dev/null +++ b/drivers/gnss/ubx.c @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * u-blox GNSS receiver driver + * + * Copyright (C) 2018 Johan Hovold + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "serial.h" + +struct ubx_data { + struct regulator *v_bckp; + struct regulator *vcc; +}; + +static int ubx_set_active(struct gnss_serial *gserial) +{ + struct ubx_data *data = gnss_serial_get_drvdata(gserial); + int ret; + + ret = regulator_enable(data->vcc); + if (ret) + return ret; + + return 0; +} + +static int ubx_set_standby(struct gnss_serial *gserial) +{ + struct ubx_data *data = gnss_serial_get_drvdata(gserial); + int ret; + + ret = regulator_disable(data->vcc); + if (ret) + return ret; + + return 0; +} + +static int ubx_set_power(struct gnss_serial *gserial, + enum gnss_serial_pm_state state) +{ + switch (state) { + case GNSS_SERIAL_ACTIVE: + return ubx_set_active(gserial); + case GNSS_SERIAL_OFF: + case GNSS_SERIAL_STANDBY: + return ubx_set_standby(gserial); + } + + return -EINVAL; +} + +const struct gnss_serial_ops ubx_gserial_ops = { + .set_power = ubx_set_power, +}; + +static int ubx_probe(struct serdev_device *serdev) +{ + struct gnss_serial *gserial; + struct ubx_data *data; + int ret; + + gserial = gnss_serial_allocate(serdev, sizeof(*data)); + if (IS_ERR(gserial)) { + ret = PTR_ERR(gserial); + return ret; + } + + gserial->ops = &ubx_gserial_ops; + + data = gnss_serial_get_drvdata(gserial); + + data->vcc = devm_regulator_get(&serdev->dev, "vcc"); + if (IS_ERR(data->vcc)) { + ret = PTR_ERR(data->vcc); + goto err_free_gserial; + } + + data->v_bckp = devm_regulator_get_optional(&serdev->dev, "v-bckp"); + if (IS_ERR(data->v_bckp)) { + ret = PTR_ERR(data->v_bckp); + if (ret == -ENODEV) + data->v_bckp = NULL; + else + goto err_free_gserial; + } + + if (data->v_bckp) { + ret = regulator_enable(data->v_bckp); + if (ret) + goto err_free_gserial; + } + + ret = gnss_serial_register(gserial); + if (ret) + goto err_disable_v_bckp; + + return 0; + +err_disable_v_bckp: + if (data->v_bckp) + regulator_disable(data->v_bckp); +err_free_gserial: + gnss_serial_free(gserial); + + return ret; +} + +static void ubx_remove(struct serdev_device *serdev) +{ + struct gnss_serial *gserial = serdev_device_get_drvdata(serdev); + struct ubx_data *data = gnss_serial_get_drvdata(gserial); + + gnss_serial_deregister(gserial); + if (data->v_bckp) + regulator_disable(data->v_bckp); + gnss_serial_free(gserial); +}; + +#ifdef CONFIG_OF +static const struct of_device_id ubx_of_match[] = { + { .compatible = "u-blox,neo-8" }, + { .compatible = "u-blox,neo-m8" }, + {}, +}; +MODULE_DEVICE_TABLE(of, ubx_of_match); +#endif + +static struct serdev_device_driver ubx_driver = { + .driver = { + .name = "gnss-ubx", + .of_match_table = of_match_ptr(ubx_of_match), + .pm = &gnss_serial_pm_ops, + }, + .probe = ubx_probe, + .remove = ubx_remove, +}; +module_serdev_device_driver(ubx_driver); + +MODULE_AUTHOR("Johan Hovold "); +MODULE_DESCRIPTION("u-blox GNSS receiver driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From 176193b7dd6ebcb4bb843f7b08663aeeabbdeacc Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Fri, 1 Jun 2018 10:22:57 +0200 Subject: dt-bindings: gnss: add sirfstar binding Add binding for SiRFstar-based GNSS receivers. Note that while four compatible-strings are initially added representing devices which differ in which I/O interfaces they support, they otherwise essentially share the same feature set. Pin and supply names vary slightly, as do some recommended timings. Note that the wakeup gpio is not intended to be used as a wakeup source, but rather to detect the current power state of the device (active or hibernate). Reviewed-by: Rob Herring Signed-off-by: Johan Hovold Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/gnss/sirfstar.txt | 45 ++++++++++++++++++++++ .../devicetree/bindings/vendor-prefixes.txt | 2 + .../devicetree/bindings/vendor-prefixes.txt.rej | 10 +++++ 3 files changed, 57 insertions(+) create mode 100644 Documentation/devicetree/bindings/gnss/sirfstar.txt create mode 100644 Documentation/devicetree/bindings/vendor-prefixes.txt.rej diff --git a/Documentation/devicetree/bindings/gnss/sirfstar.txt b/Documentation/devicetree/bindings/gnss/sirfstar.txt new file mode 100644 index 000000000000..648d183cdb77 --- /dev/null +++ b/Documentation/devicetree/bindings/gnss/sirfstar.txt @@ -0,0 +1,45 @@ +SiRFstar-based GNSS Receiver DT binding + +SiRFstar chipsets are used in GNSS-receiver modules produced by several +vendors and can use UART, SPI or I2C interfaces. + +Please see Documentation/devicetree/bindings/gnss/gnss.txt for generic +properties. + +Required properties: + +- compatible : Must be one of + + "fastrax,uc430" + "linx,r4" + "wi2wi,w2sg0008i" + "wi2wi,w2sg0084i" + +- vcc-supply : Main voltage regulator (pin name: 3V3_IN, VCC, VDD) + +Required properties (I2C): +- reg : I2C slave address + +Required properties (SPI): +- reg : SPI chip select address + +Optional properties: + +- sirf,onoff-gpios : GPIO used to power on and off device (pin name: ON_OFF) +- sirf,wakeup-gpios : GPIO used to determine device power state + (pin name: RFPWRUP, WAKEUP) +- timepulse-gpios : Time pulse GPIO (pin name: 1PPS, TM) + +Example: + +serial@1234 { + compatible = "ns16550a"; + + gnss { + compatible = "wi2wi,w2sg0084i"; + + vcc-supply = <&gnss_reg>; + sirf,onoff-gpios = <&gpio0 16 GPIO_ACTIVE_HIGH>; + sirf,wakeup-gpios = <&gpio0 17 GPIO_ACTIVE_HIGH>; + }; +}; diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt index 4f61ca1b38e4..c1a15cccb404 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.txt +++ b/Documentation/devicetree/bindings/vendor-prefixes.txt @@ -124,6 +124,7 @@ excito Excito ezchip EZchip Semiconductor fairphone Fairphone B.V. faraday Faraday Technology Corporation +fastrax Fastrax Oy fcs Fairchild Semiconductor firefly Firefly focaltech FocalTech Systems Co.,Ltd @@ -203,6 +204,7 @@ licheepi Lichee Pi linaro Linaro Limited linksys Belkin International, Inc. (Linksys) linux Linux-specific binding +linx Linx Technologies lltc Linear Technology Corporation logicpd Logic PD, Inc. lsi LSI Corp. (LSI Logic) diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt.rej b/Documentation/devicetree/bindings/vendor-prefixes.txt.rej new file mode 100644 index 000000000000..a32acd9ce2b2 --- /dev/null +++ b/Documentation/devicetree/bindings/vendor-prefixes.txt.rej @@ -0,0 +1,10 @@ +--- Documentation/devicetree/bindings/vendor-prefixes.txt ++++ Documentation/devicetree/bindings/vendor-prefixes.txt +@@ -395,6 +397,7 @@ vot Vision Optical Technology Co., Ltd. + wd Western Digital Corp. + wetek WeTek Electronics, limited. + wexler Wexler ++wi2wi Wi2Wi, Inc. + winbond Winbond Electronics corp. + winstar Winstar Display Corp. + wlf Wolfson Microelectronics -- cgit v1.2.3 From d2efbbd18b1e26ac79401841089e4a4b97d745c3 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Fri, 1 Jun 2018 10:22:58 +0200 Subject: gnss: add driver for sirfstar-based receivers Add driver for serial-connected SiRFstar-based GNSS receivers. These devices typically boot into hibernate mode from which they can be woken using a pulse on the ON_OFF input pin. Once active, a pulse on the same ON_OFF pin is used to put the device back into hibernate mode. The current state can be determined by sampling the WAKEUP output. Hardware configurations where WAKEUP has been connected to ON_OFF (and where an initial WAKEUP pulse during boot is sufficient to have the device boot into active mode) are also supported. In this case, device power is managed using the main-supply regulator only. Note that configurations where WAKEUP is left not connected, so that the device power state can only indirectly be determined using the I/O interface, is currently not supported. It should be fairly straight-forward to extend the current implementation with such support however (and this this is the main reason for not using the generic serial implementation for this driver). Note that timepulse-support is left unimplemented. Signed-off-by: Johan Hovold Signed-off-by: Greg Kroah-Hartman --- drivers/gnss/Kconfig | 12 ++ drivers/gnss/Makefile | 3 + drivers/gnss/sirf.c | 407 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 422 insertions(+) create mode 100644 drivers/gnss/sirf.c diff --git a/drivers/gnss/Kconfig b/drivers/gnss/Kconfig index 784b8c0367d9..6abc88514512 100644 --- a/drivers/gnss/Kconfig +++ b/drivers/gnss/Kconfig @@ -15,6 +15,18 @@ if GNSS config GNSS_SERIAL tristate +config GNSS_SIRF_SERIAL + tristate "SiRFstar GNSS receiver support" + depends on SERIAL_DEV_BUS + ---help--- + Say Y here if you have a SiRFstar-based GNSS receiver which uses a + serial interface. + + To compile this driver as a module, choose M here: the module will + be called gnss-sirf. + + If unsure, say N. + config GNSS_UBX_SERIAL tristate "u-blox GNSS receiver support" depends on SERIAL_DEV_BUS diff --git a/drivers/gnss/Makefile b/drivers/gnss/Makefile index d9295b20b7bc..5cf0ebe0330a 100644 --- a/drivers/gnss/Makefile +++ b/drivers/gnss/Makefile @@ -9,5 +9,8 @@ gnss-y := core.o obj-$(CONFIG_GNSS_SERIAL) += gnss-serial.o gnss-serial-y := serial.o +obj-$(CONFIG_GNSS_SIRF_SERIAL) += gnss-sirf.o +gnss-sirf-y := sirf.o + obj-$(CONFIG_GNSS_UBX_SERIAL) += gnss-ubx.o gnss-ubx-y := ubx.o diff --git a/drivers/gnss/sirf.c b/drivers/gnss/sirf.c new file mode 100644 index 000000000000..5fb0f730db48 --- /dev/null +++ b/drivers/gnss/sirf.c @@ -0,0 +1,407 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * SiRFstar GNSS receiver driver + * + * Copyright (C) 2018 Johan Hovold + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SIRF_BOOT_DELAY 500 +#define SIRF_ON_OFF_PULSE_TIME 100 +#define SIRF_ACTIVATE_TIMEOUT 200 +#define SIRF_HIBERNATE_TIMEOUT 200 + +struct sirf_data { + struct gnss_device *gdev; + struct serdev_device *serdev; + speed_t speed; + struct regulator *vcc; + struct gpio_desc *on_off; + struct gpio_desc *wakeup; + int irq; + bool active; + wait_queue_head_t power_wait; +}; + +static int sirf_open(struct gnss_device *gdev) +{ + struct sirf_data *data = gnss_get_drvdata(gdev); + struct serdev_device *serdev = data->serdev; + int ret; + + ret = serdev_device_open(serdev); + if (ret) + return ret; + + serdev_device_set_baudrate(serdev, data->speed); + serdev_device_set_flow_control(serdev, false); + + ret = pm_runtime_get_sync(&serdev->dev); + if (ret < 0) { + dev_err(&gdev->dev, "failed to runtime resume: %d\n", ret); + pm_runtime_put_noidle(&serdev->dev); + goto err_close; + } + + return 0; + +err_close: + serdev_device_close(serdev); + + return ret; +} + +static void sirf_close(struct gnss_device *gdev) +{ + struct sirf_data *data = gnss_get_drvdata(gdev); + struct serdev_device *serdev = data->serdev; + + serdev_device_close(serdev); + + pm_runtime_put(&serdev->dev); +} + +static int sirf_write_raw(struct gnss_device *gdev, const unsigned char *buf, + size_t count) +{ + struct sirf_data *data = gnss_get_drvdata(gdev); + struct serdev_device *serdev = data->serdev; + int ret; + + /* write is only buffered synchronously */ + ret = serdev_device_write(serdev, buf, count, 0); + if (ret < 0) + return ret; + + /* FIXME: determine if interrupted? */ + serdev_device_wait_until_sent(serdev, 0); + + return count; +} + +static const struct gnss_operations sirf_gnss_ops = { + .open = sirf_open, + .close = sirf_close, + .write_raw = sirf_write_raw, +}; + +static int sirf_receive_buf(struct serdev_device *serdev, + const unsigned char *buf, size_t count) +{ + struct sirf_data *data = serdev_device_get_drvdata(serdev); + struct gnss_device *gdev = data->gdev; + + return gnss_insert_raw(gdev, buf, count); +} + +static const struct serdev_device_ops sirf_serdev_ops = { + .receive_buf = sirf_receive_buf, + .write_wakeup = serdev_device_write_wakeup, +}; + +static irqreturn_t sirf_wakeup_handler(int irq, void *dev_id) +{ + struct sirf_data *data = dev_id; + struct device *dev = &data->serdev->dev; + int ret; + + ret = gpiod_get_value_cansleep(data->wakeup); + dev_dbg(dev, "%s - wakeup = %d\n", __func__, ret); + if (ret < 0) + goto out; + + data->active = !!ret; + wake_up_interruptible(&data->power_wait); +out: + return IRQ_HANDLED; +} + +static int sirf_wait_for_power_state(struct sirf_data *data, bool active, + unsigned long timeout) +{ + int ret; + + ret = wait_event_interruptible_timeout(data->power_wait, + data->active == active, msecs_to_jiffies(timeout)); + if (ret < 0) + return ret; + + if (ret == 0) { + dev_warn(&data->serdev->dev, "timeout waiting for active state = %d\n", + active); + return -ETIMEDOUT; + } + + return 0; +} + +static void sirf_pulse_on_off(struct sirf_data *data) +{ + gpiod_set_value_cansleep(data->on_off, 1); + msleep(SIRF_ON_OFF_PULSE_TIME); + gpiod_set_value_cansleep(data->on_off, 0); +} + +static int sirf_set_active(struct sirf_data *data, bool active) +{ + unsigned long timeout; + int retries = 3; + int ret; + + if (active) + timeout = SIRF_ACTIVATE_TIMEOUT; + else + timeout = SIRF_HIBERNATE_TIMEOUT; + + while (retries-- > 0) { + sirf_pulse_on_off(data); + ret = sirf_wait_for_power_state(data, active, timeout); + if (ret < 0) { + if (ret == -ETIMEDOUT) + continue; + + return ret; + } + + break; + } + + if (retries == 0) + return -ETIMEDOUT; + + return 0; +} + +static int sirf_runtime_suspend(struct device *dev) +{ + struct sirf_data *data = dev_get_drvdata(dev); + + if (!data->on_off) + return regulator_disable(data->vcc); + + return sirf_set_active(data, false); +} + +static int sirf_runtime_resume(struct device *dev) +{ + struct sirf_data *data = dev_get_drvdata(dev); + + if (!data->on_off) + return regulator_enable(data->vcc); + + return sirf_set_active(data, true); +} + +static int __maybe_unused sirf_suspend(struct device *dev) +{ + struct sirf_data *data = dev_get_drvdata(dev); + int ret = 0; + + if (!pm_runtime_suspended(dev)) + ret = sirf_runtime_suspend(dev); + + if (data->wakeup) + disable_irq(data->irq); + + return ret; +} + +static int __maybe_unused sirf_resume(struct device *dev) +{ + struct sirf_data *data = dev_get_drvdata(dev); + int ret = 0; + + if (data->wakeup) + enable_irq(data->irq); + + if (!pm_runtime_suspended(dev)) + ret = sirf_runtime_resume(dev); + + return ret; +} + +static const struct dev_pm_ops sirf_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(sirf_suspend, sirf_resume) + SET_RUNTIME_PM_OPS(sirf_runtime_suspend, sirf_runtime_resume, NULL) +}; + +static int sirf_parse_dt(struct serdev_device *serdev) +{ + struct sirf_data *data = serdev_device_get_drvdata(serdev); + struct device_node *node = serdev->dev.of_node; + u32 speed = 9600; + + of_property_read_u32(node, "current-speed", &speed); + + data->speed = speed; + + return 0; +} + +static int sirf_probe(struct serdev_device *serdev) +{ + struct device *dev = &serdev->dev; + struct gnss_device *gdev; + struct sirf_data *data; + int ret; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + gdev = gnss_allocate_device(dev); + if (!gdev) + return -ENOMEM; + + gdev->ops = &sirf_gnss_ops; + gnss_set_drvdata(gdev, data); + + data->serdev = serdev; + data->gdev = gdev; + + init_waitqueue_head(&data->power_wait); + + serdev_device_set_drvdata(serdev, data); + serdev_device_set_client_ops(serdev, &sirf_serdev_ops); + + ret = sirf_parse_dt(serdev); + if (ret) + goto err_put_device; + + data->vcc = devm_regulator_get(dev, "vcc"); + if (IS_ERR(data->vcc)) { + ret = PTR_ERR(data->vcc); + goto err_put_device; + } + + data->on_off = devm_gpiod_get_optional(dev, "sirf,onoff", + GPIOD_OUT_LOW); + if (IS_ERR(data->on_off)) + goto err_put_device; + + if (data->on_off) { + data->wakeup = devm_gpiod_get_optional(dev, "sirf,wakeup", + GPIOD_IN); + if (IS_ERR(data->wakeup)) + goto err_put_device; + + /* + * Configurations where WAKEUP has been left not connected, + * are currently not supported. + */ + if (!data->wakeup) { + dev_err(dev, "no wakeup gpio specified\n"); + ret = -ENODEV; + goto err_put_device; + } + } + + if (data->wakeup) { + ret = gpiod_to_irq(data->wakeup); + if (ret < 0) + goto err_put_device; + + data->irq = ret; + + ret = devm_request_threaded_irq(dev, data->irq, NULL, + sirf_wakeup_handler, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "wakeup", data); + if (ret) + goto err_put_device; + } + + if (data->on_off) { + ret = regulator_enable(data->vcc); + if (ret) + goto err_put_device; + + /* Wait for chip to boot into hibernate mode */ + msleep(SIRF_BOOT_DELAY); + } + + if (IS_ENABLED(CONFIG_PM)) { + pm_runtime_set_suspended(dev); /* clear runtime_error flag */ + pm_runtime_enable(dev); + } else { + ret = sirf_runtime_resume(dev); + if (ret < 0) + goto err_disable_vcc; + } + + ret = gnss_register_device(gdev); + if (ret) + goto err_disable_rpm; + + return 0; + +err_disable_rpm: + if (IS_ENABLED(CONFIG_PM)) + pm_runtime_disable(dev); + else + sirf_runtime_suspend(dev); +err_disable_vcc: + if (data->on_off) + regulator_disable(data->vcc); +err_put_device: + gnss_put_device(data->gdev); + + return ret; +} + +static void sirf_remove(struct serdev_device *serdev) +{ + struct sirf_data *data = serdev_device_get_drvdata(serdev); + + gnss_deregister_device(data->gdev); + + if (IS_ENABLED(CONFIG_PM)) + pm_runtime_disable(&serdev->dev); + else + sirf_runtime_suspend(&serdev->dev); + + if (data->on_off) + regulator_disable(data->vcc); + + gnss_put_device(data->gdev); +}; + +#ifdef CONFIG_OF +static const struct of_device_id sirf_of_match[] = { + { .compatible = "fastrax,uc430" }, + { .compatible = "linx,r4" }, + { .compatible = "wi2wi,w2sg0008i" }, + { .compatible = "wi2wi,w2sg0084i" }, + {}, +}; +MODULE_DEVICE_TABLE(of, sirf_of_match); +#endif + +static struct serdev_device_driver sirf_driver = { + .driver = { + .name = "gnss-sirf", + .of_match_table = of_match_ptr(sirf_of_match), + .pm = &sirf_pm_ops, + }, + .probe = sirf_probe, + .remove = sirf_remove, +}; +module_serdev_device_driver(sirf_driver); + +MODULE_AUTHOR("Johan Hovold "); +MODULE_DESCRIPTION("SiRFstar GNSS receiver driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From 10f146639fee5ffaf7cf0081c1af518f7d0c533c Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Fri, 1 Jun 2018 10:22:59 +0200 Subject: gnss: add receiver type support Add a "type" device attribute and a "GNSS_TYPE" uevent variable which can be used to determine the type of a GNSS receiver. The currently identified types reflect the protocol(s) supported by a receiver: "NMEA" NMEA 0183 "SiRF" SiRF Binary "UBX" UBX Note that both SiRF and UBX type receivers typically support a subset of NMEA 0183 with vendor extensions (e.g. to allow switching to the vendor protocol). Signed-off-by: Johan Hovold Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-class-gnss | 15 +++++++++ MAINTAINERS | 1 + drivers/gnss/core.c | 49 ++++++++++++++++++++++++++++++ drivers/gnss/sirf.c | 1 + drivers/gnss/ubx.c | 2 ++ include/linux/gnss.h | 9 ++++++ 6 files changed, 77 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-class-gnss diff --git a/Documentation/ABI/testing/sysfs-class-gnss b/Documentation/ABI/testing/sysfs-class-gnss new file mode 100644 index 000000000000..2467b6900eae --- /dev/null +++ b/Documentation/ABI/testing/sysfs-class-gnss @@ -0,0 +1,15 @@ +What: /sys/class/gnss/gnssN/type +Date: May 2018 +KernelVersion: 4.18 +Contact: Johan Hovold +Description: + The GNSS receiver type. The currently identified types reflect + the protocol(s) supported by the receiver: + + "NMEA" NMEA 0183 + "SiRF" SiRF Binary + "UBX" UBX + + Note that also non-"NMEA" type receivers typically support a + subset of NMEA 0183 with vendor extensions (e.g. to allow + switching to a vendor protocol). diff --git a/MAINTAINERS b/MAINTAINERS index f980c6186094..e01d220a6f05 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6041,6 +6041,7 @@ F: include/uapi/linux/gigaset_dev.h GNSS SUBSYSTEM M: Johan Hovold S: Maintained +F: Documentation/ABI/testing/sysfs-class-gnss F: Documentation/devicetree/bindings/gnss/ F: drivers/gnss/ F: include/linux/gnss.h diff --git a/drivers/gnss/core.c b/drivers/gnss/core.c index 307894ca2725..f30ef8338b3a 100644 --- a/drivers/gnss/core.c +++ b/drivers/gnss/core.c @@ -330,6 +330,52 @@ int gnss_insert_raw(struct gnss_device *gdev, const unsigned char *buf, } EXPORT_SYMBOL_GPL(gnss_insert_raw); +static const char * const gnss_type_names[GNSS_TYPE_COUNT] = { + [GNSS_TYPE_NMEA] = "NMEA", + [GNSS_TYPE_SIRF] = "SiRF", + [GNSS_TYPE_UBX] = "UBX", +}; + +static const char *gnss_type_name(struct gnss_device *gdev) +{ + const char *name = NULL; + + if (gdev->type < GNSS_TYPE_COUNT) + name = gnss_type_names[gdev->type]; + + if (!name) + dev_WARN(&gdev->dev, "type name not defined\n"); + + return name; +} + +static ssize_t type_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct gnss_device *gdev = to_gnss_device(dev); + + return sprintf(buf, "%s\n", gnss_type_name(gdev)); +} +static DEVICE_ATTR_RO(type); + +static struct attribute *gnss_attrs[] = { + &dev_attr_type.attr, + NULL, +}; +ATTRIBUTE_GROUPS(gnss); + +static int gnss_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + struct gnss_device *gdev = to_gnss_device(dev); + int ret; + + ret = add_uevent_var(env, "GNSS_TYPE=%s", gnss_type_name(gdev)); + if (ret) + return ret; + + return 0; +} + static int __init gnss_module_init(void) { int ret; @@ -347,6 +393,9 @@ static int __init gnss_module_init(void) goto err_unregister_chrdev; } + gnss_class->dev_groups = gnss_groups; + gnss_class->dev_uevent = gnss_uevent; + pr_info("GNSS driver registered with major %d\n", MAJOR(gnss_first)); return 0; diff --git a/drivers/gnss/sirf.c b/drivers/gnss/sirf.c index 5fb0f730db48..79cb98950013 100644 --- a/drivers/gnss/sirf.c +++ b/drivers/gnss/sirf.c @@ -267,6 +267,7 @@ static int sirf_probe(struct serdev_device *serdev) if (!gdev) return -ENOMEM; + gdev->type = GNSS_TYPE_SIRF; gdev->ops = &sirf_gnss_ops; gnss_set_drvdata(gdev, data); diff --git a/drivers/gnss/ubx.c b/drivers/gnss/ubx.c index ecddfb362a6f..902b6854b7db 100644 --- a/drivers/gnss/ubx.c +++ b/drivers/gnss/ubx.c @@ -77,6 +77,8 @@ static int ubx_probe(struct serdev_device *serdev) gserial->ops = &ubx_gserial_ops; + gserial->gdev->type = GNSS_TYPE_UBX; + data = gnss_serial_get_drvdata(gserial); data->vcc = devm_regulator_get(&serdev->dev, "vcc"); diff --git a/include/linux/gnss.h b/include/linux/gnss.h index e26aeac1e0e2..43546977098c 100644 --- a/include/linux/gnss.h +++ b/include/linux/gnss.h @@ -18,6 +18,14 @@ struct gnss_device; +enum gnss_type { + GNSS_TYPE_NMEA = 0, + GNSS_TYPE_SIRF, + GNSS_TYPE_UBX, + + GNSS_TYPE_COUNT +}; + struct gnss_operations { int (*open)(struct gnss_device *gdev); void (*close)(struct gnss_device *gdev); @@ -30,6 +38,7 @@ struct gnss_device { struct cdev cdev; int id; + enum gnss_type type; unsigned long flags; struct rw_semaphore rwsem; -- cgit v1.2.3 From 09755690c6b7c1eabdc4651eb3b276f8feb1e447 Mon Sep 17 00:00:00 2001 From: Nadav Amit Date: Tue, 19 Jun 2018 16:00:24 -0700 Subject: vmw_balloon: fix inflation of 64-bit GFNs When balloon batching is not supported by the hypervisor, the guest frame number (GFN) must fit in 32-bit. However, due to a bug, this check was mistakenly ignored. In practice, when total RAM is greater than 16TB, the balloon does not work currently, making this bug unlikely to happen. Fixes: ef0f8f112984 ("VMware balloon: partially inline vmballoon_reserve_page.") Cc: stable@vger.kernel.org Reviewed-by: Xavier Deguillard Signed-off-by: Nadav Amit Signed-off-by: Greg Kroah-Hartman --- drivers/misc/vmw_balloon.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/drivers/misc/vmw_balloon.c b/drivers/misc/vmw_balloon.c index efd733472a35..28e77ab1e136 100644 --- a/drivers/misc/vmw_balloon.c +++ b/drivers/misc/vmw_balloon.c @@ -450,7 +450,7 @@ static int vmballoon_send_lock_page(struct vmballoon *b, unsigned long pfn, pfn32 = (u32)pfn; if (pfn32 != pfn) - return -1; + return -EINVAL; STATS_INC(b->stats.lock[false]); @@ -460,7 +460,7 @@ static int vmballoon_send_lock_page(struct vmballoon *b, unsigned long pfn, pr_debug("%s - ppn %lx, hv returns %ld\n", __func__, pfn, status); STATS_INC(b->stats.lock_fail[false]); - return 1; + return -EIO; } static int vmballoon_send_batched_lock(struct vmballoon *b, @@ -597,11 +597,12 @@ static int vmballoon_lock_page(struct vmballoon *b, unsigned int num_pages, locked = vmballoon_send_lock_page(b, page_to_pfn(page), &hv_status, target); - if (locked > 0) { + if (locked) { STATS_INC(b->stats.refused_alloc[false]); - if (hv_status == VMW_BALLOON_ERROR_RESET || - hv_status == VMW_BALLOON_ERROR_PPN_NOTNEEDED) { + if (locked == -EIO && + (hv_status == VMW_BALLOON_ERROR_RESET || + hv_status == VMW_BALLOON_ERROR_PPN_NOTNEEDED)) { vmballoon_free_page(page, false); return -EIO; } @@ -617,7 +618,7 @@ static int vmballoon_lock_page(struct vmballoon *b, unsigned int num_pages, } else { vmballoon_free_page(page, false); } - return -EIO; + return locked; } /* track allocated page */ -- cgit v1.2.3 From 5081efd112560d3febb328e627176235b250d59d Mon Sep 17 00:00:00 2001 From: Nadav Amit Date: Tue, 19 Jun 2018 16:00:25 -0700 Subject: vmw_balloon: do not use 2MB without batching If the hypervisor sets 2MB batching is on, while batching is cleared, the balloon code breaks. In this case the legacy mechanism is used with 2MB page. The VM would report a 2MB page is ballooned, and the hypervisor would only take the first 4KB. While the hypervisor should not report such settings, make the code more robust by not enabling 2MB support without batching. Fixes: 365bd7ef7ec8e ("VMware balloon: Support 2m page ballooning.") Cc: stable@vger.kernel.org Reviewed-by: Xavier Deguillard Signed-off-by: Nadav Amit Signed-off-by: Greg Kroah-Hartman --- drivers/misc/vmw_balloon.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/misc/vmw_balloon.c b/drivers/misc/vmw_balloon.c index 28e77ab1e136..60ab83d3d0ef 100644 --- a/drivers/misc/vmw_balloon.c +++ b/drivers/misc/vmw_balloon.c @@ -341,7 +341,13 @@ static bool vmballoon_send_start(struct vmballoon *b, unsigned long req_caps) success = false; } - if (b->capabilities & VMW_BALLOON_BATCHED_2M_CMDS) + /* + * 2MB pages are only supported with batching. If batching is for some + * reason disabled, do not use 2MB pages, since otherwise the legacy + * mechanism is used with 2MB pages, causing a failure. + */ + if ((b->capabilities & VMW_BALLOON_BATCHED_2M_CMDS) && + (b->capabilities & VMW_BALLOON_BATCHED_CMDS)) b->supported_page_sizes = 2; else b->supported_page_sizes = 1; -- cgit v1.2.3 From ce664331b2487a5d244a51cbdd8cb54f866fbe5d Mon Sep 17 00:00:00 2001 From: Nadav Amit Date: Tue, 19 Jun 2018 16:00:26 -0700 Subject: vmw_balloon: VMCI_DOORBELL_SET does not check status When vmballoon_vmci_init() sets a doorbell using VMCI_DOORBELL_SET, for some reason it does not consider the status and looks at the result. However, the hypervisor does not update the result - it updates the status. This might cause VMCI doorbell not to be enabled, resulting in degraded performance. Fixes: 48e3d668b790 ("VMware balloon: Enable notification via VMCI") Cc: stable@vger.kernel.org Reviewed-by: Xavier Deguillard Signed-off-by: Nadav Amit Signed-off-by: Greg Kroah-Hartman --- drivers/misc/vmw_balloon.c | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/drivers/misc/vmw_balloon.c b/drivers/misc/vmw_balloon.c index 60ab83d3d0ef..a7df4c24a28d 100644 --- a/drivers/misc/vmw_balloon.c +++ b/drivers/misc/vmw_balloon.c @@ -1036,29 +1036,30 @@ static void vmballoon_vmci_cleanup(struct vmballoon *b) */ static int vmballoon_vmci_init(struct vmballoon *b) { - int error = 0; + unsigned long error, dummy; - if ((b->capabilities & VMW_BALLOON_SIGNALLED_WAKEUP_CMD) != 0) { - error = vmci_doorbell_create(&b->vmci_doorbell, - VMCI_FLAG_DELAYED_CB, - VMCI_PRIVILEGE_FLAG_RESTRICTED, - vmballoon_doorbell, b); - - if (error == VMCI_SUCCESS) { - VMWARE_BALLOON_CMD(VMCI_DOORBELL_SET, - b->vmci_doorbell.context, - b->vmci_doorbell.resource, error); - STATS_INC(b->stats.doorbell_set); - } - } + if ((b->capabilities & VMW_BALLOON_SIGNALLED_WAKEUP_CMD) == 0) + return 0; - if (error != 0) { - vmballoon_vmci_cleanup(b); + error = vmci_doorbell_create(&b->vmci_doorbell, VMCI_FLAG_DELAYED_CB, + VMCI_PRIVILEGE_FLAG_RESTRICTED, + vmballoon_doorbell, b); - return -EIO; - } + if (error != VMCI_SUCCESS) + goto fail; + + error = VMWARE_BALLOON_CMD(VMCI_DOORBELL_SET, b->vmci_doorbell.context, + b->vmci_doorbell.resource, dummy); + + STATS_INC(b->stats.doorbell_set); + + if (error != VMW_BALLOON_SUCCESS) + goto fail; return 0; +fail: + vmballoon_vmci_cleanup(b); + return -EIO; } /* -- cgit v1.2.3 From c3cc1b0fc27508da53fe955a3b23d03964410682 Mon Sep 17 00:00:00 2001 From: Nadav Amit Date: Tue, 19 Jun 2018 16:00:27 -0700 Subject: vmw_balloon: fix VMCI use when balloon built into kernel Currently, when all modules, including VMCI and VMware balloon are built into the kernel, the initialization of the balloon happens before the VMCI is probed. As a result, the balloon fails to initialize the VMCI doorbell, which it uses to get asynchronous requests for balloon size changes. The problem can be seen in the logs, in the form of the following message: "vmw_balloon: failed to initialize vmci doorbell" The driver would work correctly but slightly less efficiently, probing for requests periodically. This patch changes the balloon to be initialized using late_initcall() instead of module_init() to address this issue. It does not address a situation in which VMCI is built as a module and the balloon is built into the kernel. Fixes: 48e3d668b790 ("VMware balloon: Enable notification via VMCI") Cc: stable@vger.kernel.org Reviewed-by: Xavier Deguillard Signed-off-by: Nadav Amit Signed-off-by: Greg Kroah-Hartman --- drivers/misc/vmw_balloon.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/misc/vmw_balloon.c b/drivers/misc/vmw_balloon.c index a7df4c24a28d..e7cfc85f6961 100644 --- a/drivers/misc/vmw_balloon.c +++ b/drivers/misc/vmw_balloon.c @@ -1297,7 +1297,14 @@ static int __init vmballoon_init(void) return 0; } -module_init(vmballoon_init); + +/* + * Using late_initcall() instead of module_init() allows the balloon to use the + * VMCI doorbell even when the balloon is built into the kernel. Otherwise the + * VMCI is probed only after the balloon is initialized. If the balloon is used + * as a module, late_initcall() is equivalent to module_init(). + */ +late_initcall(vmballoon_init); static void __exit vmballoon_exit(void) { -- cgit v1.2.3 From ec992cc7ea40e46e53aa8d38bc39db205de9cd34 Mon Sep 17 00:00:00 2001 From: Nadav Amit Date: Tue, 19 Jun 2018 16:00:28 -0700 Subject: vmw_balloon: remove inflation rate limiting Since commit 33d268ed0019 ("VMware balloon: Do not limit the amount of frees and allocations in non-sleep mode."), the allocations are not increased, and therefore balloon inflation rate limiting is in practice broken. While we can restore rate limiting, in practice we see that it can result in adverse effect, as the hypervisor throttles down the VM if it does not respond well enough, or alternatively causes it to perform very poorly as the host swaps out the VM memory. Throttling the VM down can even have a cascading effect, in which the VM reclaims memory even slower and consequentially throttled down even further. We therefore remove all the rate limiting mechanisms, including the slow allocation cycles, as they are likely to do more harm than good. Fixes: 33d268ed0019 ("VMware balloon: Do not limit the amount of frees and allocations in non-sleep mode.") Reviewed-by: Xavier Deguillard Signed-off-by: Nadav Amit Signed-off-by: Greg Kroah-Hartman --- drivers/misc/vmw_balloon.c | 92 ++++++---------------------------------------- 1 file changed, 11 insertions(+), 81 deletions(-) diff --git a/drivers/misc/vmw_balloon.c b/drivers/misc/vmw_balloon.c index e7cfc85f6961..400a1ccefc8e 100644 --- a/drivers/misc/vmw_balloon.c +++ b/drivers/misc/vmw_balloon.c @@ -54,25 +54,6 @@ MODULE_ALIAS("dmi:*:svnVMware*:*"); MODULE_ALIAS("vmware_vmmemctl"); MODULE_LICENSE("GPL"); -/* - * Various constants controlling rate of inflaint/deflating balloon, - * measured in pages. - */ - -/* - * Rates of memory allocaton when guest experiences memory pressure - * (driver performs sleeping allocations). - */ -#define VMW_BALLOON_RATE_ALLOC_MIN 512U -#define VMW_BALLOON_RATE_ALLOC_MAX 2048U -#define VMW_BALLOON_RATE_ALLOC_INC 16U - -/* - * When guest is under memory pressure, use a reduced page allocation - * rate for next several cycles. - */ -#define VMW_BALLOON_SLOW_CYCLES 4 - /* * Use __GFP_HIGHMEM to allow pages from HIGHMEM zone. We don't * allow wait (__GFP_RECLAIM) for NOSLEEP page allocations. Use @@ -284,12 +265,6 @@ struct vmballoon { /* reset flag */ bool reset_required; - /* adjustment rates (pages per second) */ - unsigned int rate_alloc; - - /* slowdown page allocations for next few cycles */ - unsigned int slow_allocation_cycles; - unsigned long capabilities; struct vmballoon_batch_page *batch_page; @@ -797,8 +772,6 @@ static void vmballoon_add_batched_page(struct vmballoon *b, int idx, */ static void vmballoon_inflate(struct vmballoon *b) { - unsigned rate; - unsigned int allocations = 0; unsigned int num_pages = 0; int error = 0; gfp_t flags = VMW_PAGE_ALLOC_NOSLEEP; @@ -825,17 +798,9 @@ static void vmballoon_inflate(struct vmballoon *b) * Start with no sleep allocation rate which may be higher * than sleeping allocation rate. */ - if (b->slow_allocation_cycles) { - rate = b->rate_alloc; - is_2m_pages = false; - } else { - rate = UINT_MAX; - is_2m_pages = - b->supported_page_sizes == VMW_BALLOON_NUM_PAGE_SIZES; - } + is_2m_pages = b->supported_page_sizes == VMW_BALLOON_NUM_PAGE_SIZES; - pr_debug("%s - goal: %d, no-sleep rate: %u, sleep rate: %d\n", - __func__, b->target - b->size, rate, b->rate_alloc); + pr_debug("%s - goal: %d", __func__, b->target - b->size); while (!b->reset_required && b->size + num_pages * vmballoon_page_size(is_2m_pages) @@ -868,31 +833,24 @@ static void vmballoon_inflate(struct vmballoon *b) if (flags == VMW_PAGE_ALLOC_CANSLEEP) { /* * CANSLEEP page allocation failed, so guest - * is under severe memory pressure. Quickly - * decrease allocation rate. + * is under severe memory pressure. We just log + * the event, but do not stop the inflation + * due to its negative impact on performance. */ - b->rate_alloc = max(b->rate_alloc / 2, - VMW_BALLOON_RATE_ALLOC_MIN); STATS_INC(b->stats.sleep_alloc_fail); break; } /* * NOSLEEP page allocation failed, so the guest is - * under memory pressure. Let us slow down page - * allocations for next few cycles so that the guest - * gets out of memory pressure. Also, if we already - * allocated b->rate_alloc pages, let's pause, - * otherwise switch to sleeping allocations. + * under memory pressure. Slowing down page alloctions + * seems to be reasonable, but doing so might actually + * cause the hypervisor to throttle us down, resulting + * in degraded performance. We will count on the + * scheduler and standard memory management mechanisms + * for now. */ - b->slow_allocation_cycles = VMW_BALLOON_SLOW_CYCLES; - - if (allocations >= b->rate_alloc) - break; - flags = VMW_PAGE_ALLOC_CANSLEEP; - /* Lower rate for sleeping allocations. */ - rate = b->rate_alloc; continue; } @@ -906,28 +864,11 @@ static void vmballoon_inflate(struct vmballoon *b) } cond_resched(); - - if (allocations >= rate) { - /* We allocated enough pages, let's take a break. */ - break; - } } if (num_pages > 0) b->ops->lock(b, num_pages, is_2m_pages, &b->target); - /* - * We reached our goal without failures so try increasing - * allocation rate. - */ - if (error == 0 && allocations >= b->rate_alloc) { - unsigned int mult = allocations / b->rate_alloc; - - b->rate_alloc = - min(b->rate_alloc + mult * VMW_BALLOON_RATE_ALLOC_INC, - VMW_BALLOON_RATE_ALLOC_MAX); - } - vmballoon_release_refused_pages(b, true); vmballoon_release_refused_pages(b, false); } @@ -1122,9 +1063,6 @@ static void vmballoon_work(struct work_struct *work) if (b->reset_required) vmballoon_reset(b); - if (b->slow_allocation_cycles > 0) - b->slow_allocation_cycles--; - if (!b->reset_required && vmballoon_send_get_target(b, &target)) { /* update target, adjust size */ b->target = target; @@ -1168,11 +1106,6 @@ static int vmballoon_debug_show(struct seq_file *f, void *offset) "current: %8d pages\n", b->target, b->size); - /* format rate info */ - seq_printf(f, - "rateSleepAlloc: %8d pages/sec\n", - b->rate_alloc); - seq_printf(f, "\n" "timer: %8u\n" @@ -1279,9 +1212,6 @@ static int __init vmballoon_init(void) INIT_LIST_HEAD(&balloon.page_sizes[is_2m_pages].refused_pages); } - /* initialize rates */ - balloon.rate_alloc = VMW_BALLOON_RATE_ALLOC_MAX; - INIT_DELAYED_WORK(&balloon.dwork, vmballoon_work); error = vmballoon_debugfs_init(&balloon); -- cgit v1.2.3 From 8b4770ec7df700bcbecc961e2ddbddf552421906 Mon Sep 17 00:00:00 2001 From: Nadav Amit Date: Tue, 19 Jun 2018 16:00:29 -0700 Subject: vmw_balloon: update copyright message Removing the GPL wording and replace it with an SPDX tag. The immediate trigger for doing it now is the need to remove the list of maintainers from the source file, as the maintainer list changed. Reviewed-by: Xavier Deguillard Signed-off-by: Nadav Amit Signed-off-by: Greg Kroah-Hartman --- drivers/misc/vmw_balloon.c | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/drivers/misc/vmw_balloon.c b/drivers/misc/vmw_balloon.c index 400a1ccefc8e..43d454c11c13 100644 --- a/drivers/misc/vmw_balloon.c +++ b/drivers/misc/vmw_balloon.c @@ -1,27 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0 /* * VMware Balloon driver. * - * Copyright (C) 2000-2014, VMware, Inc. All Rights Reserved. + * Copyright (C) 2000-2018, VMware, Inc. 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 as published by the - * Free Software Foundation; version 2 of the License and no later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or - * NON INFRINGEMENT. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Maintained by: Xavier Deguillard - * Philip Moltmann - */ - -/* * This is VMware physical memory management driver for Linux. The driver * acts like a "balloon" that can be inflated to reclaim physical pages by * reserving them in the guest and invalidating them in the monitor, -- cgit v1.2.3 From 9cf04a29ff701d259fdcaf45baf92087898c6f61 Mon Sep 17 00:00:00 2001 From: Nadav Amit Date: Tue, 19 Jun 2018 16:00:30 -0700 Subject: vmw_balloon: update maintainers list Philip Moltman is no longer a maintainer of the VMware balloon. Setting Nadav Amit as one instead. Reviewed-by: Xavier Deguillard Signed-off-by: Nadav Amit Signed-off-by: Greg Kroah-Hartman --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index e01d220a6f05..4b1169b8af88 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15302,7 +15302,7 @@ F: include/linux/vme* VMWARE BALLOON DRIVER M: Xavier Deguillard -M: Philip Moltmann +M: Nadav Amit M: "VMware, Inc." L: linux-kernel@vger.kernel.org S: Maintained -- cgit v1.2.3 From ccbaef5d0ba8b0015ac94d8e65957bc1f5864513 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Tue, 3 Jul 2018 08:14:43 +0100 Subject: misc: genwqe: remove several redundant variables The variables val16, type, pci_dev and type are set but are never used hence they are redundant and can be removed. Cleans up clang warnings: warning: variable 'type' set but not used [-Wunused-but-set-variable] warning: variable 'val16' set but not used [-Wunused-but-set-variable] warning: variable 'pci_dev' set but not used [-Wunused-but-set-variable] warning: variable 'type' set but not used [-Wunused-but-set-variable] Signed-off-by: Colin Ian King Signed-off-by: Greg Kroah-Hartman --- drivers/misc/genwqe/card_debugfs.c | 4 ---- drivers/misc/genwqe/card_dev.c | 5 ----- 2 files changed, 9 deletions(-) diff --git a/drivers/misc/genwqe/card_debugfs.c b/drivers/misc/genwqe/card_debugfs.c index f921dd590271..c6b82f09b3ba 100644 --- a/drivers/misc/genwqe/card_debugfs.c +++ b/drivers/misc/genwqe/card_debugfs.c @@ -305,7 +305,6 @@ GENWQE_DEBUGFS_RO(ddcb_info, genwqe_ddcb_info_show); static int genwqe_info_show(struct seq_file *s, void *unused) { struct genwqe_dev *cd = s->private; - u16 val16, type; u64 app_id, slu_id, bitstream = -1; struct pci_dev *pci_dev = cd->pci_dev; @@ -315,9 +314,6 @@ static int genwqe_info_show(struct seq_file *s, void *unused) if (genwqe_is_privileged(cd)) bitstream = __genwqe_readq(cd, IO_SLU_BITSTREAM); - val16 = (u16)(slu_id & 0x0fLLU); - type = (u16)((slu_id >> 20) & 0xffLLU); - seq_printf(s, "%s driver version: %s\n" " Device Name/Type: %s %s CardIdx: %d\n" " SLU/APP Config : 0x%016llx/0x%016llx\n" diff --git a/drivers/misc/genwqe/card_dev.c b/drivers/misc/genwqe/card_dev.c index 0dd6b5ef314a..f453ab82f0d7 100644 --- a/drivers/misc/genwqe/card_dev.c +++ b/drivers/misc/genwqe/card_dev.c @@ -304,14 +304,12 @@ static int genwqe_open(struct inode *inode, struct file *filp) { struct genwqe_dev *cd; struct genwqe_file *cfile; - struct pci_dev *pci_dev; cfile = kzalloc(sizeof(*cfile), GFP_KERNEL); if (cfile == NULL) return -ENOMEM; cd = container_of(inode->i_cdev, struct genwqe_dev, cdev_genwqe); - pci_dev = cd->pci_dev; cfile->cd = cd; cfile->filp = filp; cfile->client = NULL; @@ -864,7 +862,6 @@ static int ddcb_cmd_fixups(struct genwqe_file *cfile, struct ddcb_requ *req) struct genwqe_dev *cd = cfile->cd; struct genwqe_ddcb_cmd *cmd = &req->cmd; struct dma_mapping *m; - const char *type = "UNKNOWN"; for (i = 0, asiv_offs = 0x00; asiv_offs <= 0x58; i++, asiv_offs += 0x08) { @@ -933,11 +930,9 @@ static int ddcb_cmd_fixups(struct genwqe_file *cfile, struct ddcb_requ *req) m = genwqe_search_pin(cfile, u_addr, u_size, NULL); if (m != NULL) { - type = "PINNING"; page_offs = (u_addr - (u64)m->u_vaddr)/PAGE_SIZE; } else { - type = "MAPPING"; m = &req->dma_mappings[i]; genwqe_mapping_init(m, -- cgit v1.2.3 From aa8998e57640c98ce0c53c1dfc7a8e990adfffa4 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Tue, 3 Jul 2018 08:23:05 +0100 Subject: sgi-xp: remove redundant pointers ch and rp The pointers ch and rp are set but are never used hence they are redundant and can be removed. Cleans up clang warnings: warning: variable 'ch' set but not used [-Wunused-but-set-variable] warning: variable 'rp' set but not used [-Wunused-but-set-variable] Signed-off-by: Colin Ian King Signed-off-by: Greg Kroah-Hartman --- drivers/misc/sgi-xp/xpc_channel.c | 3 --- drivers/misc/sgi-xp/xpc_partition.c | 3 --- 2 files changed, 6 deletions(-) diff --git a/drivers/misc/sgi-xp/xpc_channel.c b/drivers/misc/sgi-xp/xpc_channel.c index 128d5615c804..05a890ce2ab8 100644 --- a/drivers/misc/sgi-xp/xpc_channel.c +++ b/drivers/misc/sgi-xp/xpc_channel.c @@ -656,7 +656,6 @@ xpc_initiate_connect(int ch_number) { short partid; struct xpc_partition *part; - struct xpc_channel *ch; DBUG_ON(ch_number < 0 || ch_number >= XPC_MAX_NCHANNELS); @@ -664,8 +663,6 @@ xpc_initiate_connect(int ch_number) part = &xpc_partitions[partid]; if (xpc_part_ref(part)) { - ch = &part->channels[ch_number]; - /* * Initiate the establishment of a connection on the * newly registered channel to the remote partition. diff --git a/drivers/misc/sgi-xp/xpc_partition.c b/drivers/misc/sgi-xp/xpc_partition.c index 7284413dabfd..4e59b01ad7eb 100644 --- a/drivers/misc/sgi-xp/xpc_partition.c +++ b/drivers/misc/sgi-xp/xpc_partition.c @@ -415,7 +415,6 @@ xpc_discovery(void) int region_size; int max_regions; int nasid; - struct xpc_rsvd_page *rp; unsigned long *discovered_nasids; enum xp_retval ret; @@ -432,8 +431,6 @@ xpc_discovery(void) return; } - rp = (struct xpc_rsvd_page *)xpc_rsvd_page; - /* * The term 'region' in this context refers to the minimum number of * nodes that can comprise an access protection grouping. The access -- cgit v1.2.3 From 53e44782e2302f6483b34539f0c3efbf5c6f9f37 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Tue, 3 Jul 2018 08:32:49 +0100 Subject: misc: ti-st: remove redundant variable 'type' Variable type is being assigned but is never used hence it is redundant and can be removed. Cleans up clang warning: warning: variable 'type' set but not used [-Wunused-but-set-variable] Signed-off-by: Colin Ian King Signed-off-by: Greg Kroah-Hartman --- drivers/misc/ti-st/st_kim.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/misc/ti-st/st_kim.c b/drivers/misc/ti-st/st_kim.c index 5ec3f5a43718..28fba5b3625e 100644 --- a/drivers/misc/ti-st/st_kim.c +++ b/drivers/misc/ti-st/st_kim.c @@ -138,7 +138,7 @@ static void kim_int_recv(struct kim_data_s *kim_gdata, const unsigned char *data, long count) { const unsigned char *ptr; - int len = 0, type = 0; + int len = 0; unsigned char *plen; pr_debug("%s", __func__); @@ -183,7 +183,6 @@ static void kim_int_recv(struct kim_data_s *kim_gdata, case 0x04: kim_gdata->rx_state = ST_W4_HEADER; kim_gdata->rx_count = 2; - type = *ptr; break; default: pr_info("unknown packet"); -- cgit v1.2.3 From c4bcaffa6b8192379eec9a8f7fe263907c4b755e Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Tue, 3 Jul 2018 08:38:08 +0100 Subject: misc: vmci: remove redundant variable is_local Variable is_local is being assigned but is never used hence it is redundant and can be removed. Cleans up clang warning: warning: variable 'is_local' set but not used [-Wunused-but-set-variable] Signed-off-by: Colin Ian King Signed-off-by: Greg Kroah-Hartman --- drivers/misc/vmw_vmci/vmci_queue_pair.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/drivers/misc/vmw_vmci/vmci_queue_pair.c b/drivers/misc/vmw_vmci/vmci_queue_pair.c index b4d7774cfe07..9ee47244a308 100644 --- a/drivers/misc/vmw_vmci/vmci_queue_pair.c +++ b/drivers/misc/vmw_vmci/vmci_queue_pair.c @@ -2214,7 +2214,6 @@ int vmci_qp_broker_map(struct vmci_handle handle, { struct qp_broker_entry *entry; const u32 context_id = vmci_ctx_get_id(context); - bool is_local = false; int result; if (vmci_handle_is_invalid(handle) || !context || @@ -2243,7 +2242,6 @@ int vmci_qp_broker_map(struct vmci_handle handle, goto out; } - is_local = entry->qp.flags & VMCI_QPFLAG_LOCAL; result = VMCI_SUCCESS; if (context_id != VMCI_HOST_CONTEXT_ID) { @@ -2325,7 +2323,6 @@ int vmci_qp_broker_unmap(struct vmci_handle handle, { struct qp_broker_entry *entry; const u32 context_id = vmci_ctx_get_id(context); - bool is_local = false; int result; if (vmci_handle_is_invalid(handle) || !context || @@ -2354,8 +2351,6 @@ int vmci_qp_broker_unmap(struct vmci_handle handle, goto out; } - is_local = entry->qp.flags & VMCI_QPFLAG_LOCAL; - if (context_id != VMCI_HOST_CONTEXT_ID) { qp_acquire_queue_mutex(entry->produce_q); result = qp_save_headers(entry); -- cgit v1.2.3 From ce7d8f8a66e6d0600e5f368233849d2e4560a732 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Fri, 1 Jun 2018 15:54:44 +0100 Subject: misc: rtsx: make several functions static Several helper functions are local to the source and do not need to be in global scope, so make them static. Cleans up sparse warnings: symbol 'rtsx_pm_power_saving' was not declared. Should it be static? symbol 'rtsx_set_l1off_sub_cfg_d0' was not declared. Should it be static? symbol 'rtsx_pm_full_on' was not declared. Should it be static? symbol 'rtsx_comm_set_ltr_latency' was not declared. Should it be static? symbol 'rtsx_pci_process_ocp' was not declared. Should it be static? symbol 'rtsx_pci_process_ocp_interrupt' was not declared. Should it be static? Signed-off-by: Colin Ian King Signed-off-by: Greg Kroah-Hartman --- drivers/misc/cardreader/rtsx_pcr.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/misc/cardreader/rtsx_pcr.c b/drivers/misc/cardreader/rtsx_pcr.c index e8f1d4bb806a..da445223f4cc 100644 --- a/drivers/misc/cardreader/rtsx_pcr.c +++ b/drivers/misc/cardreader/rtsx_pcr.c @@ -80,7 +80,7 @@ static inline void rtsx_pci_disable_aspm(struct rtsx_pcr *pcr) 0xFC, 0); } -int rtsx_comm_set_ltr_latency(struct rtsx_pcr *pcr, u32 latency) +static int rtsx_comm_set_ltr_latency(struct rtsx_pcr *pcr, u32 latency) { rtsx_pci_write_register(pcr, MSGTXDATA0, MASK_8_BIT_DEF, (u8) (latency & 0xFF)); @@ -143,7 +143,7 @@ int rtsx_set_l1off_sub(struct rtsx_pcr *pcr, u8 val) return 0; } -void rtsx_set_l1off_sub_cfg_d0(struct rtsx_pcr *pcr, int active) +static void rtsx_set_l1off_sub_cfg_d0(struct rtsx_pcr *pcr, int active) { if (pcr->ops->set_l1off_cfg_sub_d0) pcr->ops->set_l1off_cfg_sub_d0(pcr, active); @@ -162,7 +162,7 @@ static void rtsx_comm_pm_full_on(struct rtsx_pcr *pcr) rtsx_set_l1off_sub_cfg_d0(pcr, 1); } -void rtsx_pm_full_on(struct rtsx_pcr *pcr) +static void rtsx_pm_full_on(struct rtsx_pcr *pcr) { if (pcr->ops->full_on) pcr->ops->full_on(pcr); @@ -967,13 +967,13 @@ static void rtsx_pci_card_detect(struct work_struct *work) pcr->slots[RTSX_MS_CARD].p_dev); } -void rtsx_pci_process_ocp(struct rtsx_pcr *pcr) +static void rtsx_pci_process_ocp(struct rtsx_pcr *pcr) { if (pcr->ops->process_ocp) pcr->ops->process_ocp(pcr); } -int rtsx_pci_process_ocp_interrupt(struct rtsx_pcr *pcr) +static int rtsx_pci_process_ocp_interrupt(struct rtsx_pcr *pcr) { if (pcr->option.ocp_en) rtsx_pci_process_ocp(pcr); @@ -1094,7 +1094,7 @@ static void rtsx_comm_pm_power_saving(struct rtsx_pcr *pcr) rtsx_enable_aspm(pcr); } -void rtsx_pm_power_saving(struct rtsx_pcr *pcr) +static void rtsx_pm_power_saving(struct rtsx_pcr *pcr) { if (pcr->ops->power_saving) pcr->ops->power_saving(pcr); -- cgit v1.2.3 From c6446c210f2b658ce5a7abc5b9763447910d751d Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Thu, 21 Jun 2018 18:08:06 +0100 Subject: w1: ds2482: make module parameter extra_config static The variable extra_config is local to the source and does not need to be in global scope, so make it static. Cleans up sparse warning: warning: symbol 'extra_config' was not declared. Should it be static? Signed-off-by: Colin Ian King Signed-off-by: Greg Kroah-Hartman --- drivers/w1/masters/ds2482.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/w1/masters/ds2482.c b/drivers/w1/masters/ds2482.c index 5b3e017d9276..8b5e598ffdb3 100644 --- a/drivers/w1/masters/ds2482.c +++ b/drivers/w1/masters/ds2482.c @@ -71,7 +71,7 @@ MODULE_PARM_DESC(active_pullup, "Active pullup (apply to all buses): " \ #define DS2482_REG_CFG_APU 0x01 /* active pull-up */ /* extra configurations - e.g. 1WS */ -int extra_config; +static int extra_config; /** * Write and verify codes for the CHANNEL_SELECT command (DS2482-800 only). -- cgit v1.2.3 From ea4ba866d14fee63df370eca5002937b69babd99 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Sat, 23 Jun 2018 23:26:54 +0100 Subject: goldfish_pipe: make tasklet goldfish_interrupt_tasklet static Tasklet goldfish_interrupt_tasklet is local to the source and does not need to be in global scope, so make it static. Cleans up sparse warning: symbol 'goldfish_interrupt_tasklet' was not declared. Should it be static? Signed-off-by: Colin Ian King Signed-off-by: Greg Kroah-Hartman --- drivers/platform/goldfish/goldfish_pipe.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/goldfish/goldfish_pipe.c b/drivers/platform/goldfish/goldfish_pipe.c index 3e32a4c14d5f..4866a8b12e98 100644 --- a/drivers/platform/goldfish/goldfish_pipe.c +++ b/drivers/platform/goldfish/goldfish_pipe.c @@ -645,7 +645,7 @@ static void goldfish_interrupt_task(unsigned long unused) wake_up_interruptible(&pipe->wake_queue); } } -DECLARE_TASKLET(goldfish_interrupt_tasklet, goldfish_interrupt_task, 0); +static DECLARE_TASKLET(goldfish_interrupt_tasklet, goldfish_interrupt_task, 0); /* * The general idea of the interrupt handling: -- cgit v1.2.3 From 9f10ee32bfaf10dc4d2b0ccd149726eda8df3d57 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Mon, 2 Jul 2018 09:21:16 +0100 Subject: hpet: remove redundant pointer hpet Pointer hpet is being assigned but is never used hence it is redundant and can be removed. Cleans up clang warning: warning: variable 'hpet' set but not used [-Wunused-but-set-variable] Signed-off-by: Colin Ian King Signed-off-by: Greg Kroah-Hartman --- drivers/char/hpet.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/char/hpet.c b/drivers/char/hpet.c index be426eb2a353..4a22b4b41aef 100644 --- a/drivers/char/hpet.c +++ b/drivers/char/hpet.c @@ -579,7 +579,6 @@ hpet_ioctl_common(struct hpet_dev *devp, unsigned int cmd, unsigned long arg, struct hpet_info *info) { struct hpet_timer __iomem *timer; - struct hpet __iomem *hpet; struct hpets *hpetp; int err; unsigned long v; @@ -591,7 +590,6 @@ hpet_ioctl_common(struct hpet_dev *devp, unsigned int cmd, unsigned long arg, case HPET_DPI: case HPET_IRQFREQ: timer = devp->hd_timer; - hpet = devp->hd_hpet; hpetp = devp->hd_hpets; break; case HPET_IE_ON: -- cgit v1.2.3 From 07fb28b80c7705c15002fe91dd4340bcd5174dbd Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Mon, 2 Jul 2018 09:31:45 +0100 Subject: char: pcmcia: remove redundant pointer dev MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pointer dev is being assigned but is never used hence it is redundant and can be removed. Cleans up clang warning: warning: variable ‘dev’ set but not used [-Wunused-but-set-variable] Signed-off-by: Colin Ian King Signed-off-by: Greg Kroah-Hartman --- drivers/char/pcmcia/cm4000_cs.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/char/pcmcia/cm4000_cs.c b/drivers/char/pcmcia/cm4000_cs.c index 370e0a64ead1..a219964cb770 100644 --- a/drivers/char/pcmcia/cm4000_cs.c +++ b/drivers/char/pcmcia/cm4000_cs.c @@ -1748,8 +1748,6 @@ static int cm4000_config_check(struct pcmcia_device *p_dev, void *priv_data) static int cm4000_config(struct pcmcia_device * link, int devno) { - struct cm4000_dev *dev; - link->config_flags |= CONF_AUTO_SET_IO; /* read the config-tuples */ @@ -1759,8 +1757,6 @@ static int cm4000_config(struct pcmcia_device * link, int devno) if (pcmcia_enable_device(link)) goto cs_release; - dev = link->priv; - return 0; cs_release: -- cgit v1.2.3 From 2e08b1dbbc3aa50cdbf975633459e8fa2660fd2e Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Tue, 3 Jul 2018 08:04:00 +0100 Subject: eeprom: idt_89hpesx: remove redundant variable csrval_len Variable csrval_len is being assigned but is never used hence it is redundant and can be removed. Cleans up clang warning: warning: variable 'csrval_len' set but not used [-Wunused-but-set-variable] Signed-off-by: Colin Ian King Signed-off-by: Greg Kroah-Hartman --- drivers/misc/eeprom/idt_89hpesx.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/misc/eeprom/idt_89hpesx.c b/drivers/misc/eeprom/idt_89hpesx.c index 59dc24bb70ec..119eb05a5a91 100644 --- a/drivers/misc/eeprom/idt_89hpesx.c +++ b/drivers/misc/eeprom/idt_89hpesx.c @@ -938,7 +938,7 @@ static ssize_t idt_dbgfs_csr_write(struct file *filep, const char __user *ubuf, { struct idt_89hpesx_dev *pdev = filep->private_data; char *colon_ch, *csraddr_str, *csrval_str; - int ret, csraddr_len, csrval_len; + int ret, csraddr_len; u32 csraddr, csrval; char *buf; @@ -974,12 +974,10 @@ static ssize_t idt_dbgfs_csr_write(struct file *filep, const char __user *ubuf, csraddr_str[csraddr_len] = '\0'; /* Register value must follow the colon */ csrval_str = colon_ch + 1; - csrval_len = strnlen(csrval_str, count - csraddr_len); } else /* if (str_colon == NULL) */ { csraddr_str = (char *)buf; /* Just to shut warning up */ csraddr_len = strnlen(csraddr_str, count); csrval_str = NULL; - csrval_len = 0; } /* Convert CSR address to u32 value */ -- cgit v1.2.3 From af0a5646cb8d03f33ef028eff5b92996e53da201 Mon Sep 17 00:00:00 2001 From: Arjan van de Ven Date: Tue, 5 Jun 2018 13:37:49 -0700 Subject: use the new async probing feature for the hyperv drivers Recent kernels support asynchronous probing; most hyperv drivers can be probed async easily so set the required flag for this. Signed-off-by: Arjan van de Ven Signed-off-by: Stephen Hemminger Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman --- drivers/hid/hid-hyperv.c | 3 +++ drivers/hv/hv_balloon.c | 3 +++ drivers/hv/hv_util.c | 3 +++ drivers/input/serio/hyperv-keyboard.c | 3 +++ drivers/net/hyperv/netvsc_drv.c | 3 +++ drivers/scsi/storvsc_drv.c | 3 +++ drivers/video/fbdev/hyperv_fb.c | 6 ++++++ 7 files changed, 24 insertions(+) diff --git a/drivers/hid/hid-hyperv.c b/drivers/hid/hid-hyperv.c index 3aa2bb9f0f81..b372854cf38d 100644 --- a/drivers/hid/hid-hyperv.c +++ b/drivers/hid/hid-hyperv.c @@ -598,6 +598,9 @@ static struct hv_driver mousevsc_drv = { .id_table = id_table, .probe = mousevsc_probe, .remove = mousevsc_remove, + .driver = { + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, }; static int __init mousevsc_init(void) diff --git a/drivers/hv/hv_balloon.c b/drivers/hv/hv_balloon.c index b3e9f13f8bc3..b1b788082793 100644 --- a/drivers/hv/hv_balloon.c +++ b/drivers/hv/hv_balloon.c @@ -1765,6 +1765,9 @@ static struct hv_driver balloon_drv = { .id_table = id_table, .probe = balloon_probe, .remove = balloon_remove, + .driver = { + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, }; static int __init init_balloon_drv(void) diff --git a/drivers/hv/hv_util.c b/drivers/hv/hv_util.c index 14dce25c104f..423205077bf6 100644 --- a/drivers/hv/hv_util.c +++ b/drivers/hv/hv_util.c @@ -487,6 +487,9 @@ static struct hv_driver util_drv = { .id_table = id_table, .probe = util_probe, .remove = util_remove, + .driver = { + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, }; static int hv_ptp_enable(struct ptp_clock_info *info, diff --git a/drivers/input/serio/hyperv-keyboard.c b/drivers/input/serio/hyperv-keyboard.c index 25151d9214e0..47a0e81a2989 100644 --- a/drivers/input/serio/hyperv-keyboard.c +++ b/drivers/input/serio/hyperv-keyboard.c @@ -424,6 +424,9 @@ static struct hv_driver hv_kbd_drv = { .id_table = id_table, .probe = hv_kbd_probe, .remove = hv_kbd_remove, + .driver = { + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, }; static int __init hv_kbd_init(void) diff --git a/drivers/net/hyperv/netvsc_drv.c b/drivers/net/hyperv/netvsc_drv.c index fe2256bf1d13..eaf33df91ac5 100644 --- a/drivers/net/hyperv/netvsc_drv.c +++ b/drivers/net/hyperv/netvsc_drv.c @@ -2188,6 +2188,9 @@ static struct hv_driver netvsc_drv = { .id_table = id_table, .probe = netvsc_probe, .remove = netvsc_remove, + .driver = { + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, }; /* diff --git a/drivers/scsi/storvsc_drv.c b/drivers/scsi/storvsc_drv.c index 33a4a4dad324..f03dc03a42c3 100644 --- a/drivers/scsi/storvsc_drv.c +++ b/drivers/scsi/storvsc_drv.c @@ -1935,6 +1935,9 @@ static struct hv_driver storvsc_drv = { .id_table = id_table, .probe = storvsc_probe, .remove = storvsc_remove, + .driver = { + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, }; #if IS_ENABLED(CONFIG_SCSI_FC_ATTRS) diff --git a/drivers/video/fbdev/hyperv_fb.c b/drivers/video/fbdev/hyperv_fb.c index 2fd49b2358f8..403d8cd3e582 100644 --- a/drivers/video/fbdev/hyperv_fb.c +++ b/drivers/video/fbdev/hyperv_fb.c @@ -912,6 +912,9 @@ static struct hv_driver hvfb_drv = { .id_table = id_table, .probe = hvfb_probe, .remove = hvfb_remove, + .driver = { + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, }; static int hvfb_pci_stub_probe(struct pci_dev *pdev, @@ -929,6 +932,9 @@ static struct pci_driver hvfb_pci_stub_driver = { .id_table = pci_stub_id_table, .probe = hvfb_pci_stub_probe, .remove = hvfb_pci_stub_remove, + .driver = { + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + } }; static int __init hvfb_drv_init(void) -- cgit v1.2.3 From e9a7fda29a5620d9ac2a750d8e35f5d270096321 Mon Sep 17 00:00:00 2001 From: Michael Kelley Date: Tue, 5 Jun 2018 13:37:50 -0700 Subject: x86/hyperv: Add interrupt handler annotations Add standard interrupt handler annotations to hyperv_vector_handler(). This does not fix any observed bug, but avoids potential removal of the code by link time optimization and makes it consistent with hv_stimer0_vector_handler in the same source file. Suggested-by: Thomas Gleixner Signed-off-by: Michael Kelley Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman --- arch/x86/kernel/cpu/mshyperv.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/x86/kernel/cpu/mshyperv.c b/arch/x86/kernel/cpu/mshyperv.c index 031082c96db8..8a49b7ef7b76 100644 --- a/arch/x86/kernel/cpu/mshyperv.c +++ b/arch/x86/kernel/cpu/mshyperv.c @@ -41,7 +41,7 @@ static void (*hv_stimer0_handler)(void); static void (*hv_kexec_handler)(void); static void (*hv_crash_handler)(struct pt_regs *regs); -void hyperv_vector_handler(struct pt_regs *regs) +__visible void __irq_entry hyperv_vector_handler(struct pt_regs *regs) { struct pt_regs *old_regs = set_irq_regs(regs); -- cgit v1.2.3 From 71b38245acb05a38d2d861792bdd99cd9f6a0f78 Mon Sep 17 00:00:00 2001 From: Michael Kelley Date: Tue, 5 Jun 2018 13:37:51 -0700 Subject: Drivers: hv: vmbus: Add comments on ring buffer signaling Add comments describing intricacies of Hyper-V ring buffer signaling code. This information is not in Hyper-V public documents, so include here to capture the knowledge for future coders. There are no code changes in this commit. Signed-off-by: Michael Kelley Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman --- drivers/hv/ring_buffer.c | 65 ++++++++++++++++++++++++++++++++++++++++-------- include/linux/hyperv.h | 31 +++++++++++++++++------ 2 files changed, 77 insertions(+), 19 deletions(-) diff --git a/drivers/hv/ring_buffer.c b/drivers/hv/ring_buffer.c index be3c8b10b84a..3e90eb91db45 100644 --- a/drivers/hv/ring_buffer.c +++ b/drivers/hv/ring_buffer.c @@ -431,7 +431,24 @@ static u32 hv_pkt_iter_bytes_read(const struct hv_ring_buffer_info *rbi, } /* - * Update host ring buffer after iterating over packets. + * Update host ring buffer after iterating over packets. If the host has + * stopped queuing new entries because it found the ring buffer full, and + * sufficient space is being freed up, signal the host. But be careful to + * only signal the host when necessary, both for performance reasons and + * because Hyper-V protects itself by throttling guests that signal + * inappropriately. + * + * Determining when to signal is tricky. There are three key data inputs + * that must be handled in this order to avoid race conditions: + * + * 1. Update the read_index + * 2. Read the pending_send_sz + * 3. Read the current write_index + * + * The interrupt_mask is not used to determine when to signal. The + * interrupt_mask is used only on the guest->host ring buffer when + * sending requests to the host. The host does not use it on the host-> + * guest ring buffer to indicate whether it should be signaled. */ void hv_pkt_iter_close(struct vmbus_channel *channel) { @@ -447,22 +464,30 @@ void hv_pkt_iter_close(struct vmbus_channel *channel) start_read_index = rbi->ring_buffer->read_index; rbi->ring_buffer->read_index = rbi->priv_read_index; + /* + * Older versions of Hyper-V (before WS2102 and Win8) do not + * implement pending_send_sz and simply poll if the host->guest + * ring buffer is full. No signaling is needed or expected. + */ if (!rbi->ring_buffer->feature_bits.feat_pending_send_sz) return; /* * Issue a full memory barrier before making the signaling decision. - * Here is the reason for having this barrier: - * If the reading of the pend_sz (in this function) - * were to be reordered and read before we commit the new read - * index (in the calling function) we could - * have a problem. If the host were to set the pending_sz after we - * have sampled pending_sz and go to sleep before we commit the + * If reading pending_send_sz were to be reordered and happen + * before we commit the new read_index, a race could occur. If the + * host were to set the pending_send_sz after we have sampled + * pending_send_sz, and the ring buffer blocks before we commit the * read index, we could miss sending the interrupt. Issue a full * memory barrier to address this. */ virt_mb(); + /* + * If the pending_send_sz is zero, then the ring buffer is not + * blocked and there is no need to signal. This is far by the + * most common case, so exit quickly for best performance. + */ pending_sz = READ_ONCE(rbi->ring_buffer->pending_send_sz); if (!pending_sz) return; @@ -476,14 +501,32 @@ void hv_pkt_iter_close(struct vmbus_channel *channel) bytes_read = hv_pkt_iter_bytes_read(rbi, start_read_index); /* - * If there was space before we began iteration, - * then host was not blocked. + * We want to signal the host only if we're transitioning + * from a "not enough free space" state to a "enough free + * space" state. For example, it's possible that this function + * could run and free up enough space to signal the host, and then + * run again and free up additional space before the host has a + * chance to clear the pending_send_sz. The 2nd invocation would + * be a null transition from "enough free space" to "enough free + * space", which doesn't warrant a signal. + * + * Exactly filling the ring buffer is treated as "not enough + * space". The ring buffer always must have at least one byte + * empty so the empty and full conditions are distinguishable. + * hv_get_bytes_to_write() doesn't fully tell the truth in + * this regard. + * + * So first check if we were in the "enough free space" state + * before we began the iteration. If so, the host was not + * blocked, and there's no need to signal. */ - if (curr_write_sz - bytes_read > pending_sz) return; - /* If pending write will not fit, don't give false hope. */ + /* + * Similarly, if the new state is "not enough space", then + * there's no need to signal. + */ if (curr_write_sz <= pending_sz) return; diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index 3a3012f57be4..2330f08062c7 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -89,18 +89,33 @@ struct hv_ring_buffer { u32 interrupt_mask; /* - * Win8 uses some of the reserved bits to implement - * interrupt driven flow management. On the send side - * we can request that the receiver interrupt the sender - * when the ring transitions from being full to being able - * to handle a message of size "pending_send_sz". + * WS2012/Win8 and later versions of Hyper-V implement interrupt + * driven flow management. The feature bit feat_pending_send_sz + * is set by the host on the host->guest ring buffer, and by the + * guest on the guest->host ring buffer. * - * Add necessary state for this enhancement. + * The meaning of the feature bit is a bit complex in that it has + * semantics that apply to both ring buffers. If the guest sets + * the feature bit in the guest->host ring buffer, the guest is + * telling the host that: + * 1) It will set the pending_send_sz field in the guest->host ring + * buffer when it is waiting for space to become available, and + * 2) It will read the pending_send_sz field in the host->guest + * ring buffer and interrupt the host when it frees enough space + * + * Similarly, if the host sets the feature bit in the host->guest + * ring buffer, the host is telling the guest that: + * 1) It will set the pending_send_sz field in the host->guest ring + * buffer when it is waiting for space to become available, and + * 2) It will read the pending_send_sz field in the guest->host + * ring buffer and interrupt the guest when it frees enough space + * + * If either the guest or host does not set the feature bit that it + * owns, that guest or host must do polling if it encounters a full + * ring buffer, and not signal the other end with an interrupt. */ u32 pending_send_sz; - u32 reserved1[12]; - union { struct { u32 feat_pending_send_sz:1; -- cgit v1.2.3 From 50229128727f7e11840ca1b2b501f880818d56b6 Mon Sep 17 00:00:00 2001 From: Dexuan Cui Date: Tue, 5 Jun 2018 13:37:52 -0700 Subject: Drivers: hv: vmbus: Fix the offer_in_progress in vmbus_process_offer() I didn't really hit a real bug, but just happened to spot the bug: we have decreased the counter at the beginning of vmbus_process_offer(), so we mustn't decrease it again. Fixes: 6f3d791f3006 ("Drivers: hv: vmbus: Fix rescind handling issues") Signed-off-by: Dexuan Cui Cc: stable@vger.kernel.org Cc: Stephen Hemminger Cc: K. Y. Srinivasan Cc: Stable # 4.14 and above Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman --- drivers/hv/channel_mgmt.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/hv/channel_mgmt.c b/drivers/hv/channel_mgmt.c index ecc2bd275a73..f3b551a50653 100644 --- a/drivers/hv/channel_mgmt.c +++ b/drivers/hv/channel_mgmt.c @@ -527,10 +527,8 @@ static void vmbus_process_offer(struct vmbus_channel *newchannel) struct hv_device *dev = newchannel->primary_channel->device_obj; - if (vmbus_add_channel_kobj(dev, newchannel)) { - atomic_dec(&vmbus_connection.offer_in_progress); + if (vmbus_add_channel_kobj(dev, newchannel)) goto err_free_chan; - } if (channel->sc_creation_callback != NULL) channel->sc_creation_callback(newchannel); -- cgit v1.2.3 From 619a4c8b2b5a0b64842b0362a152a63072615a54 Mon Sep 17 00:00:00 2001 From: Michael Kelley Date: Tue, 5 Jun 2018 13:37:53 -0700 Subject: Drivers: hv: vmbus: Remove x86 MSR refs in arch independent code In architecture independent code for manipulating Hyper-V synthetic timers and synthetic interrupts, pass in an ordinal number identifying the timer or interrupt, rather than an actual MSR register address. Then in x86/x64 specific code, map the ordinal number to the appropriate MSR. This change facilitates the introduction of an ARM64 version of Hyper-V, which uses the same synthetic timers and interrupts, but a different mechanism for accessing them. Signed-off-by: Michael Kelley Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman --- arch/x86/include/asm/mshyperv.h | 12 ++++++++---- drivers/hv/hv.c | 20 ++++++++------------ 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/arch/x86/include/asm/mshyperv.h b/arch/x86/include/asm/mshyperv.h index 3cd14311edfa..81e768b8d9eb 100644 --- a/arch/x86/include/asm/mshyperv.h +++ b/arch/x86/include/asm/mshyperv.h @@ -75,8 +75,10 @@ static inline void vmbus_signal_eom(struct hv_message *msg, u32 old_msg_type) } } -#define hv_init_timer(timer, tick) wrmsrl(timer, tick) -#define hv_init_timer_config(config, val) wrmsrl(config, val) +#define hv_init_timer(timer, tick) \ + wrmsrl(HV_X64_MSR_STIMER0_COUNT + (2*timer), tick) +#define hv_init_timer_config(timer, val) \ + wrmsrl(HV_X64_MSR_STIMER0_CONFIG + (2*timer), val) #define hv_get_simp(val) rdmsrl(HV_X64_MSR_SIMP, val) #define hv_set_simp(val) wrmsrl(HV_X64_MSR_SIMP, val) @@ -89,8 +91,10 @@ static inline void vmbus_signal_eom(struct hv_message *msg, u32 old_msg_type) #define hv_get_vp_index(index) rdmsrl(HV_X64_MSR_VP_INDEX, index) -#define hv_get_synint_state(int_num, val) rdmsrl(int_num, val) -#define hv_set_synint_state(int_num, val) wrmsrl(int_num, val) +#define hv_get_synint_state(int_num, val) \ + rdmsrl(HV_X64_MSR_SINT0 + int_num, val) +#define hv_set_synint_state(int_num, val) \ + wrmsrl(HV_X64_MSR_SINT0 + int_num, val) void hyperv_callback_vector(void); void hyperv_reenlightenment_vector(void); diff --git a/drivers/hv/hv.c b/drivers/hv/hv.c index 658dc765753b..4547fd83a17f 100644 --- a/drivers/hv/hv.c +++ b/drivers/hv/hv.c @@ -127,14 +127,14 @@ static int hv_ce_set_next_event(unsigned long delta, current_tick = hyperv_cs->read(NULL); current_tick += delta; - hv_init_timer(HV_X64_MSR_STIMER0_COUNT, current_tick); + hv_init_timer(0, current_tick); return 0; } static int hv_ce_shutdown(struct clock_event_device *evt) { - hv_init_timer(HV_X64_MSR_STIMER0_COUNT, 0); - hv_init_timer_config(HV_X64_MSR_STIMER0_CONFIG, 0); + hv_init_timer(0, 0); + hv_init_timer_config(0, 0); if (direct_mode_enabled) hv_disable_stimer0_percpu_irq(stimer0_irq); @@ -164,7 +164,7 @@ static int hv_ce_set_oneshot(struct clock_event_device *evt) timer_cfg.direct_mode = 0; timer_cfg.sintx = VMBUS_MESSAGE_SINT; } - hv_init_timer_config(HV_X64_MSR_STIMER0_CONFIG, timer_cfg.as_uint64); + hv_init_timer_config(0, timer_cfg.as_uint64); return 0; } @@ -298,8 +298,7 @@ int hv_synic_init(unsigned int cpu) hv_set_siefp(siefp.as_uint64); /* Setup the shared SINT. */ - hv_get_synint_state(HV_X64_MSR_SINT0 + VMBUS_MESSAGE_SINT, - shared_sint.as_uint64); + hv_get_synint_state(VMBUS_MESSAGE_SINT, shared_sint.as_uint64); shared_sint.vector = HYPERVISOR_CALLBACK_VECTOR; shared_sint.masked = false; @@ -308,8 +307,7 @@ int hv_synic_init(unsigned int cpu) else shared_sint.auto_eoi = true; - hv_set_synint_state(HV_X64_MSR_SINT0 + VMBUS_MESSAGE_SINT, - shared_sint.as_uint64); + hv_set_synint_state(VMBUS_MESSAGE_SINT, shared_sint.as_uint64); /* Enable the global synic bit */ hv_get_synic_state(sctrl.as_uint64); @@ -405,15 +403,13 @@ int hv_synic_cleanup(unsigned int cpu) put_cpu_ptr(hv_cpu); } - hv_get_synint_state(HV_X64_MSR_SINT0 + VMBUS_MESSAGE_SINT, - shared_sint.as_uint64); + hv_get_synint_state(VMBUS_MESSAGE_SINT, shared_sint.as_uint64); shared_sint.masked = 1; /* Need to correctly cleanup in the case of SMP!!! */ /* Disable the interrupt */ - hv_set_synint_state(HV_X64_MSR_SINT0 + VMBUS_MESSAGE_SINT, - shared_sint.as_uint64); + hv_set_synint_state(VMBUS_MESSAGE_SINT, shared_sint.as_uint64); hv_get_simp(simp.as_uint64); simp.simp_enabled = 0; -- cgit v1.2.3 From 8a99c920092f444cf9f1d737ae76527102886d8e Mon Sep 17 00:00:00 2001 From: Olaf Hering Date: Tue, 5 Jun 2018 13:37:55 -0700 Subject: tools: hv: update lsvmbus to be compatible with python3 Python3 changed the way how 'print' works. Adjust the code to a syntax that is understood by python2 and python3. Signed-off-by: Olaf Hering Acked-by: Dexuan Cui Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman --- tools/hv/lsvmbus | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tools/hv/lsvmbus b/tools/hv/lsvmbus index 353e56768df8..55e7374bade0 100644 --- a/tools/hv/lsvmbus +++ b/tools/hv/lsvmbus @@ -17,7 +17,7 @@ if options.verbose is not None: vmbus_sys_path = '/sys/bus/vmbus/devices' if not os.path.isdir(vmbus_sys_path): - print "%s doesn't exist: exiting..." % vmbus_sys_path + print("%s doesn't exist: exiting..." % vmbus_sys_path) exit(-1) vmbus_dev_dict = { @@ -93,11 +93,11 @@ format2 = '%2s: Class_ID = %s - %s\n\tDevice_ID = %s\n\tSysfs path: %s\n%s' for d in vmbus_dev_list: if verbose == 0: - print ('VMBUS ID ' + format0) % (d.vmbus_id, d.dev_desc) + print(('VMBUS ID ' + format0) % (d.vmbus_id, d.dev_desc)) elif verbose == 1: - print ('VMBUS ID ' + format1) % \ - (d.vmbus_id, d.class_id, d.dev_desc, d.chn_vp_mapping) + print (('VMBUS ID ' + format1) % \ + (d.vmbus_id, d.class_id, d.dev_desc, d.chn_vp_mapping)) else: - print ('VMBUS ID ' + format2) % \ + print (('VMBUS ID ' + format2) % \ (d.vmbus_id, d.class_id, d.dev_desc, \ - d.device_id, d.sysfs_path, d.chn_vp_mapping) + d.device_id, d.sysfs_path, d.chn_vp_mapping)) -- cgit v1.2.3 From 07136793ccad877727afada2b0c926031f10cf52 Mon Sep 17 00:00:00 2001 From: Vitaly Kuznetsov Date: Tue, 5 Jun 2018 13:37:56 -0700 Subject: Tools: hv: vss: fix loop device detection Commit ea81fdf0981d ("Tools: hv: vss: Skip freezing filesystems backed by loop") added skip for filesystems backed by loop device. However, it seems the detection of such cases is incomplete. It was found that with 'devicemapper' storage driver docker creates the following chain: NAME MAJ:MIN loop0 7:0 ..docker-8:4-8473394-pool 253:0 ..docker-8:4-8473394-eac... 253:1 so when we're looking at the mounted device we see major '253' and not '7'. Solve the issue by walking /sys/dev/block/*/slaves chain and checking if there's a loop device somewhere. Other than that, don't skip mountpoints silently when stat() fails. In case e.g. SELinux is failing stat we don't want to skip freezing everything without letting user know about the failure. Fixes: ea81fdf0981d ("Tools: hv: vss: Skip freezing filesystems backed by loop") Signed-off-by: Vitaly Kuznetsov Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman --- tools/hv/hv_vss_daemon.c | 65 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 61 insertions(+), 4 deletions(-) diff --git a/tools/hv/hv_vss_daemon.c b/tools/hv/hv_vss_daemon.c index 34031a297f02..b13300172762 100644 --- a/tools/hv/hv_vss_daemon.c +++ b/tools/hv/hv_vss_daemon.c @@ -36,6 +36,8 @@ #include #include #include +#include +#include /* Don't use syslog() in the function since that can cause write to disk */ static int vss_do_freeze(char *dir, unsigned int cmd) @@ -68,6 +70,55 @@ static int vss_do_freeze(char *dir, unsigned int cmd) return !!ret; } +static bool is_dev_loop(const char *blkname) +{ + char *buffer; + DIR *dir; + struct dirent *entry; + bool ret = false; + + buffer = malloc(PATH_MAX); + if (!buffer) { + syslog(LOG_ERR, "Can't allocate memory!"); + exit(1); + } + + snprintf(buffer, PATH_MAX, "%s/loop", blkname); + if (!access(buffer, R_OK | X_OK)) { + ret = true; + goto free_buffer; + } else if (errno != ENOENT) { + syslog(LOG_ERR, "Can't access: %s; error:%d %s!", + buffer, errno, strerror(errno)); + } + + snprintf(buffer, PATH_MAX, "%s/slaves", blkname); + dir = opendir(buffer); + if (!dir) { + if (errno != ENOENT) + syslog(LOG_ERR, "Can't opendir: %s; error:%d %s!", + buffer, errno, strerror(errno)); + goto free_buffer; + } + + while ((entry = readdir(dir)) != NULL) { + if (strcmp(entry->d_name, ".") == 0 || + strcmp(entry->d_name, "..") == 0) + continue; + + snprintf(buffer, PATH_MAX, "%s/slaves/%s", blkname, + entry->d_name); + if (is_dev_loop(buffer)) { + ret = true; + break; + } + } + closedir(dir); +free_buffer: + free(buffer); + return ret; +} + static int vss_operate(int operation) { char match[] = "/dev/"; @@ -75,6 +126,7 @@ static int vss_operate(int operation) struct mntent *ent; struct stat sb; char errdir[1024] = {0}; + char blkdir[23]; /* /sys/dev/block/XXX:XXX */ unsigned int cmd; int error = 0, root_seen = 0, save_errno = 0; @@ -96,10 +148,15 @@ static int vss_operate(int operation) while ((ent = getmntent(mounts))) { if (strncmp(ent->mnt_fsname, match, strlen(match))) continue; - if (stat(ent->mnt_fsname, &sb) == -1) - continue; - if (S_ISBLK(sb.st_mode) && major(sb.st_rdev) == LOOP_MAJOR) - continue; + if (stat(ent->mnt_fsname, &sb)) { + syslog(LOG_ERR, "Can't stat: %s; error:%d %s!", + ent->mnt_fsname, errno, strerror(errno)); + } else { + sprintf(blkdir, "/sys/dev/block/%d:%d", + major(sb.st_rdev), minor(sb.st_rdev)); + if (is_dev_loop(blkdir)) + continue; + } if (hasmntopt(ent, MNTOPT_RO) != NULL) continue; if (strcmp(ent->mnt_type, "vfat") == 0) -- cgit v1.2.3 From 7dc9b6b8089d2ea510943fc6841c641ea9f50ab1 Mon Sep 17 00:00:00 2001 From: Michael Kelley Date: Tue, 5 Jun 2018 13:37:54 -0700 Subject: Drivers: hv: vmbus: Make TLFS #define names architecture neutral The Hyper-V feature and hint flags in hyperv-tlfs.h are all defined with the string "X64" in the name. Some of these flags are indeed x86/x64 specific, but others are not. For the ones that are used in architecture independent Hyper-V driver code, or will be used in the upcoming support for Hyper-V for ARM64, this patch removes the "X64" from the name. This patch changes the flags that are currently known to be used on multiple architectures. Hyper-V for ARM64 is still a work-in-progress and the Top Level Functional Spec (TLFS) has not been separated into x86/x64 and ARM64 areas. So additional flags may need to be updated later. This patch only changes symbol names. There are no functional changes. Signed-off-by: Michael Kelley Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman --- arch/x86/hyperv/hv_init.c | 4 ++-- arch/x86/include/asm/hyperv-tlfs.h | 12 ++++++------ arch/x86/kernel/cpu/mshyperv.c | 4 ++-- drivers/hv/hv.c | 10 +++++----- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/arch/x86/hyperv/hv_init.c b/arch/x86/hyperv/hv_init.c index 4c431e1c1eff..595e44e8abaa 100644 --- a/arch/x86/hyperv/hv_init.c +++ b/arch/x86/hyperv/hv_init.c @@ -330,7 +330,7 @@ void __init hyperv_init(void) * Register Hyper-V specific clocksource. */ #ifdef CONFIG_HYPERV_TSCPAGE - if (ms_hyperv.features & HV_X64_MSR_REFERENCE_TSC_AVAILABLE) { + if (ms_hyperv.features & HV_MSR_REFERENCE_TSC_AVAILABLE) { union hv_x64_msr_hypercall_contents tsc_msr; tsc_pg = __vmalloc(PAGE_SIZE, GFP_KERNEL, PAGE_KERNEL); @@ -359,7 +359,7 @@ register_msr_cs: */ hyperv_cs = &hyperv_cs_msr; - if (ms_hyperv.features & HV_X64_MSR_TIME_REF_COUNT_AVAILABLE) + if (ms_hyperv.features & HV_MSR_TIME_REF_COUNT_AVAILABLE) clocksource_register_hz(&hyperv_cs_msr, NSEC_PER_SEC/100); return; diff --git a/arch/x86/include/asm/hyperv-tlfs.h b/arch/x86/include/asm/hyperv-tlfs.h index b8c89265baf0..96272e99b64e 100644 --- a/arch/x86/include/asm/hyperv-tlfs.h +++ b/arch/x86/include/asm/hyperv-tlfs.h @@ -35,9 +35,9 @@ /* VP Runtime (HV_X64_MSR_VP_RUNTIME) available */ #define HV_X64_MSR_VP_RUNTIME_AVAILABLE (1 << 0) /* Partition Reference Counter (HV_X64_MSR_TIME_REF_COUNT) available*/ -#define HV_X64_MSR_TIME_REF_COUNT_AVAILABLE (1 << 1) +#define HV_MSR_TIME_REF_COUNT_AVAILABLE (1 << 1) /* Partition reference TSC MSR is available */ -#define HV_X64_MSR_REFERENCE_TSC_AVAILABLE (1 << 9) +#define HV_MSR_REFERENCE_TSC_AVAILABLE (1 << 9) /* A partition's reference time stamp counter (TSC) page */ #define HV_X64_MSR_REFERENCE_TSC 0x40000021 @@ -60,7 +60,7 @@ * Synthetic Timer MSRs (HV_X64_MSR_STIMER0_CONFIG through * HV_X64_MSR_STIMER3_COUNT) available */ -#define HV_X64_MSR_SYNTIMER_AVAILABLE (1 << 3) +#define HV_MSR_SYNTIMER_AVAILABLE (1 << 3) /* * APIC access MSRs (HV_X64_MSR_EOI, HV_X64_MSR_ICR and HV_X64_MSR_TPR) * are available @@ -86,7 +86,7 @@ #define HV_FEATURE_GUEST_CRASH_MSR_AVAILABLE (1 << 10) /* stimer Direct Mode is available */ -#define HV_X64_STIMER_DIRECT_MODE_AVAILABLE (1 << 19) +#define HV_STIMER_DIRECT_MODE_AVAILABLE (1 << 19) /* * Feature identification: EBX indicates which flags were specified at @@ -160,9 +160,9 @@ #define HV_X64_RELAXED_TIMING_RECOMMENDED (1 << 5) /* - * Virtual APIC support + * Recommend not using Auto End-Of-Interrupt feature */ -#define HV_X64_DEPRECATING_AEOI_RECOMMENDED (1 << 9) +#define HV_DEPRECATING_AEOI_RECOMMENDED (1 << 9) /* * Recommend using cluster IPI hypercalls. diff --git a/arch/x86/kernel/cpu/mshyperv.c b/arch/x86/kernel/cpu/mshyperv.c index 8a49b7ef7b76..ad12733f6058 100644 --- a/arch/x86/kernel/cpu/mshyperv.c +++ b/arch/x86/kernel/cpu/mshyperv.c @@ -50,7 +50,7 @@ __visible void __irq_entry hyperv_vector_handler(struct pt_regs *regs) if (vmbus_handler) vmbus_handler(); - if (ms_hyperv.hints & HV_X64_DEPRECATING_AEOI_RECOMMENDED) + if (ms_hyperv.hints & HV_DEPRECATING_AEOI_RECOMMENDED) ack_APIC_irq(); exiting_irq(); @@ -300,7 +300,7 @@ static void __init ms_hyperv_init_platform(void) hyperv_reenlightenment_vector); /* Setup the IDT for stimer0 */ - if (ms_hyperv.misc_features & HV_X64_STIMER_DIRECT_MODE_AVAILABLE) + if (ms_hyperv.misc_features & HV_STIMER_DIRECT_MODE_AVAILABLE) alloc_intr_gate(HYPERV_STIMER0_VECTOR, hv_stimer0_callback_vector); #endif diff --git a/drivers/hv/hv.c b/drivers/hv/hv.c index 4547fd83a17f..312fe5ed7c40 100644 --- a/drivers/hv/hv.c +++ b/drivers/hv/hv.c @@ -64,7 +64,7 @@ int hv_init(void) return -ENOMEM; direct_mode_enabled = ms_hyperv.misc_features & - HV_X64_STIMER_DIRECT_MODE_AVAILABLE; + HV_STIMER_DIRECT_MODE_AVAILABLE; return 0; } @@ -302,7 +302,7 @@ int hv_synic_init(unsigned int cpu) shared_sint.vector = HYPERVISOR_CALLBACK_VECTOR; shared_sint.masked = false; - if (ms_hyperv.hints & HV_X64_DEPRECATING_AEOI_RECOMMENDED) + if (ms_hyperv.hints & HV_DEPRECATING_AEOI_RECOMMENDED) shared_sint.auto_eoi = false; else shared_sint.auto_eoi = true; @@ -320,7 +320,7 @@ int hv_synic_init(unsigned int cpu) /* * Register the per-cpu clockevent source. */ - if (ms_hyperv.features & HV_X64_MSR_SYNTIMER_AVAILABLE) + if (ms_hyperv.features & HV_MSR_SYNTIMER_AVAILABLE) clockevents_config_and_register(hv_cpu->clk_evt, HV_TIMER_FREQUENCY, HV_MIN_DELTA_TICKS, @@ -335,7 +335,7 @@ void hv_synic_clockevents_cleanup(void) { int cpu; - if (!(ms_hyperv.features & HV_X64_MSR_SYNTIMER_AVAILABLE)) + if (!(ms_hyperv.features & HV_MSR_SYNTIMER_AVAILABLE)) return; if (direct_mode_enabled) @@ -394,7 +394,7 @@ int hv_synic_cleanup(unsigned int cpu) return -EBUSY; /* Turn off clockevent device */ - if (ms_hyperv.features & HV_X64_MSR_SYNTIMER_AVAILABLE) { + if (ms_hyperv.features & HV_MSR_SYNTIMER_AVAILABLE) { struct hv_per_cpu_context *hv_cpu = this_cpu_ptr(hv_context.cpu_context); -- cgit v1.2.3 From 3458657f9e1041838d6c3d1bccf251f14dbb1b29 Mon Sep 17 00:00:00 2001 From: Alexander Usyskin Date: Thu, 7 Jun 2018 00:31:47 +0300 Subject: mei: remove unused MEI_IAMTHIF_STALL_TIMER define MEI_IAMTHIF_STALL_TIMER is unused now and can be safely removed. Signed-off-by: Alexander Usyskin Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/hw.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/misc/mei/hw.h b/drivers/misc/mei/hw.h index 5c8286b40b62..3b325d955fbe 100644 --- a/drivers/misc/mei/hw.h +++ b/drivers/misc/mei/hw.h @@ -28,8 +28,6 @@ #define MEI_CL_CONNECT_TIMEOUT 15 /* HPS: Client Connect Timeout */ #define MEI_CLIENTS_INIT_TIMEOUT 15 /* HPS: Clients Enumeration Timeout */ -#define MEI_IAMTHIF_STALL_TIMER 12 /* HPS */ - #define MEI_PGI_TIMEOUT 1 /* PG Isolation time response 1 sec */ #define MEI_D0I3_TIMEOUT 5 /* D0i3 set/unset max response time */ #define MEI_HBM_TIMEOUT 1 /* 1 second */ -- cgit v1.2.3 From 9a7c0b69b6e765c655133ef530876aea3da71d58 Mon Sep 17 00:00:00 2001 From: Alexander Usyskin Date: Mon, 25 Jun 2018 00:11:40 +0300 Subject: mei: add optional timeout to internal bus recv Add optional timeout to internal bus recv function to enable break out of internal flows in case of no answer from FW. Signed-off-by: Alexander Usyskin Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/bus-fixup.c | 2 +- drivers/misc/mei/bus.c | 36 ++++++++++++++++++++++++++---------- drivers/misc/mei/mei_dev.h | 2 +- 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/drivers/misc/mei/bus-fixup.c b/drivers/misc/mei/bus-fixup.c index 0208c4b027c5..b17c46a43812 100644 --- a/drivers/misc/mei/bus-fixup.c +++ b/drivers/misc/mei/bus-fixup.c @@ -266,7 +266,7 @@ static int mei_nfc_if_version(struct mei_cl *cl, return -ENOMEM; ret = 0; - bytes_recv = __mei_cl_recv(cl, (u8 *)reply, if_version_length, 0); + bytes_recv = __mei_cl_recv(cl, (u8 *)reply, if_version_length, 0, 0); if (bytes_recv < if_version_length) { dev_err(bus->dev, "Could not read IF version\n"); ret = -EIO; diff --git a/drivers/misc/mei/bus.c b/drivers/misc/mei/bus.c index b1133739fb4b..7bba62a72921 100644 --- a/drivers/misc/mei/bus.c +++ b/drivers/misc/mei/bus.c @@ -116,11 +116,12 @@ out: * @buf: buffer to receive * @length: buffer length * @mode: io mode + * @timeout: recv timeout, 0 for infinite timeout * * Return: read size in bytes of < 0 on error */ ssize_t __mei_cl_recv(struct mei_cl *cl, u8 *buf, size_t length, - unsigned int mode) + unsigned int mode, unsigned long timeout) { struct mei_device *bus; struct mei_cl_cb *cb; @@ -158,13 +159,28 @@ ssize_t __mei_cl_recv(struct mei_cl *cl, u8 *buf, size_t length, mutex_unlock(&bus->device_lock); - if (wait_event_interruptible(cl->rx_wait, - (!list_empty(&cl->rd_completed)) || - (!mei_cl_is_connected(cl)))) { - - if (signal_pending(current)) - return -EINTR; - return -ERESTARTSYS; + if (timeout) { + rets = wait_event_interruptible_timeout + (cl->rx_wait, + (!list_empty(&cl->rd_completed)) || + (!mei_cl_is_connected(cl)), + msecs_to_jiffies(timeout)); + if (rets == 0) + return -ETIME; + if (rets < 0) { + if (signal_pending(current)) + return -EINTR; + return -ERESTARTSYS; + } + } else { + if (wait_event_interruptible + (cl->rx_wait, + (!list_empty(&cl->rd_completed)) || + (!mei_cl_is_connected(cl)))) { + if (signal_pending(current)) + return -EINTR; + return -ERESTARTSYS; + } } mutex_lock(&bus->device_lock); @@ -231,7 +247,7 @@ ssize_t mei_cldev_recv_nonblock(struct mei_cl_device *cldev, u8 *buf, { struct mei_cl *cl = cldev->cl; - return __mei_cl_recv(cl, buf, length, MEI_CL_IO_RX_NONBLOCK); + return __mei_cl_recv(cl, buf, length, MEI_CL_IO_RX_NONBLOCK, 0); } EXPORT_SYMBOL_GPL(mei_cldev_recv_nonblock); @@ -248,7 +264,7 @@ ssize_t mei_cldev_recv(struct mei_cl_device *cldev, u8 *buf, size_t length) { struct mei_cl *cl = cldev->cl; - return __mei_cl_recv(cl, buf, length, 0); + return __mei_cl_recv(cl, buf, length, 0, 0); } EXPORT_SYMBOL_GPL(mei_cldev_recv); diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index be9c48415da9..4058ab5ad2d8 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -317,7 +317,7 @@ void mei_cl_bus_dev_fixup(struct mei_cl_device *dev); ssize_t __mei_cl_send(struct mei_cl *cl, u8 *buf, size_t length, unsigned int mode); ssize_t __mei_cl_recv(struct mei_cl *cl, u8 *buf, size_t length, - unsigned int mode); + unsigned int mode, unsigned long timeout); bool mei_cl_bus_rx_event(struct mei_cl *cl); bool mei_cl_bus_notify_event(struct mei_cl *cl); void mei_cl_bus_remove_devices(struct mei_device *bus); -- cgit v1.2.3 From 3cfaeb33530592b02b2ceb76b379364c55ca612e Mon Sep 17 00:00:00 2001 From: Alexander Usyskin Date: Mon, 25 Jun 2018 00:11:41 +0300 Subject: mei: expose fw version to sysfs The ME FW version is constantly used by detection and update tools. To improve the reliability and simplify these tools provide a sysfs interface to access version of the platform ME firmware in the following format: :.... There can be up to three such blocks for different FW components. Signed-off-by: Alexander Usyskin Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-class-mei | 11 ++++ drivers/misc/mei/bus-fixup.c | 86 ++++++++++++++++++++++++++++--- drivers/misc/mei/main.c | 30 ++++++++++- drivers/misc/mei/mei_dev.h | 25 ++++++++- 4 files changed, 144 insertions(+), 8 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-class-mei b/Documentation/ABI/testing/sysfs-class-mei index 81ff6abf9673..17d7444a2397 100644 --- a/Documentation/ABI/testing/sysfs-class-mei +++ b/Documentation/ABI/testing/sysfs-class-mei @@ -54,3 +54,14 @@ Description: Configure tx queue limit Set maximal number of pending writes per opened session. + +What: /sys/class/mei/meiN/fw_ver +Date: May 2018 +KernelVersion: 4.18 +Contact: Tomas Winkler +Description: Display the ME firmware version. + + The version of the platform ME firmware is in format: + :.... + There can be up to three such blocks for different + FW components. diff --git a/drivers/misc/mei/bus-fixup.c b/drivers/misc/mei/bus-fixup.c index b17c46a43812..e45fe826d87d 100644 --- a/drivers/misc/mei/bus-fixup.c +++ b/drivers/misc/mei/bus-fixup.c @@ -1,7 +1,7 @@ /* * * Intel Management Engine Interface (Intel MEI) Linux driver - * Copyright (c) 2003-2013, Intel Corporation. + * Copyright (c) 2003-2018, 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, @@ -96,8 +96,22 @@ struct mkhi_fwcaps { u8 data[0]; } __packed; +struct mkhi_fw_ver_block { + u16 minor; + u8 major; + u8 platform; + u16 buildno; + u16 hotfix; +} __packed; + +struct mkhi_fw_ver { + struct mkhi_fw_ver_block ver[MEI_MAX_FW_VER_BLOCKS]; +} __packed; + #define MKHI_FWCAPS_GROUP_ID 0x3 #define MKHI_FWCAPS_SET_OS_VER_APP_RULE_CMD 6 +#define MKHI_GEN_GROUP_ID 0xFF +#define MKHI_GEN_GET_FW_VERSION_CMD 0x2 struct mkhi_msg_hdr { u8 group_id; u8 command; @@ -139,21 +153,81 @@ static int mei_osver(struct mei_cl_device *cldev) return __mei_cl_send(cldev->cl, buf, size, mode); } +#define MKHI_FWVER_BUF_LEN (sizeof(struct mkhi_msg_hdr) + \ + sizeof(struct mkhi_fw_ver)) +#define MKHI_FWVER_LEN(__num) (sizeof(struct mkhi_msg_hdr) + \ + sizeof(struct mkhi_fw_ver_block) * (__num)) +#define MKHI_RCV_TIMEOUT 500 /* receive timeout in msec */ +static int mei_fwver(struct mei_cl_device *cldev) +{ + char buf[MKHI_FWVER_BUF_LEN]; + struct mkhi_msg *req; + struct mkhi_fw_ver *fwver; + int bytes_recv, ret, i; + + memset(buf, 0, sizeof(buf)); + + req = (struct mkhi_msg *)buf; + req->hdr.group_id = MKHI_GEN_GROUP_ID; + req->hdr.command = MKHI_GEN_GET_FW_VERSION_CMD; + + ret = __mei_cl_send(cldev->cl, buf, sizeof(struct mkhi_msg_hdr), + MEI_CL_IO_TX_BLOCKING); + if (ret < 0) { + dev_err(&cldev->dev, "Could not send ReqFWVersion cmd\n"); + return ret; + } + + ret = 0; + bytes_recv = __mei_cl_recv(cldev->cl, buf, sizeof(buf), 0, + MKHI_RCV_TIMEOUT); + if (bytes_recv < MKHI_FWVER_LEN(1)) { + /* + * Should be at least one version block, + * error out if nothing found + */ + dev_err(&cldev->dev, "Could not read FW version\n"); + return -EIO; + } + + fwver = (struct mkhi_fw_ver *)req->data; + memset(cldev->bus->fw_ver, 0, sizeof(cldev->bus->fw_ver)); + for (i = 0; i < MEI_MAX_FW_VER_BLOCKS; i++) { + if (bytes_recv < MKHI_FWVER_LEN(i + 1)) + break; + dev_dbg(&cldev->dev, "FW version%d %d:%d.%d.%d.%d\n", + i, fwver->ver[i].platform, + fwver->ver[i].major, fwver->ver[i].minor, + fwver->ver[i].hotfix, fwver->ver[i].buildno); + + cldev->bus->fw_ver[i].platform = fwver->ver[i].platform; + cldev->bus->fw_ver[i].major = fwver->ver[i].major; + cldev->bus->fw_ver[i].minor = fwver->ver[i].minor; + cldev->bus->fw_ver[i].hotfix = fwver->ver[i].hotfix; + cldev->bus->fw_ver[i].buildno = fwver->ver[i].buildno; + } + + return ret; +} + static void mei_mkhi_fix(struct mei_cl_device *cldev) { int ret; - if (!cldev->bus->hbm_f_os_supported) - return; - ret = mei_cldev_enable(cldev); if (ret) return; - ret = mei_osver(cldev); + ret = mei_fwver(cldev); if (ret < 0) - dev_err(&cldev->dev, "OS version command failed %d\n", ret); + dev_err(&cldev->dev, "FW version command failed %d\n", ret); + if (cldev->bus->hbm_f_os_supported) { + ret = mei_osver(cldev); + if (ret < 0) + dev_err(&cldev->dev, "OS version command failed %d\n", + ret); + } mei_cldev_disable(cldev); } diff --git a/drivers/misc/mei/main.c b/drivers/misc/mei/main.c index 7465f17e1559..f690918f7817 100644 --- a/drivers/misc/mei/main.c +++ b/drivers/misc/mei/main.c @@ -1,7 +1,7 @@ /* * * Intel Management Engine Interface (Intel MEI) Linux driver - * Copyright (c) 2003-2012, Intel Corporation. + * Copyright (c) 2003-2018, 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, @@ -812,11 +812,39 @@ static ssize_t tx_queue_limit_store(struct device *device, } static DEVICE_ATTR_RW(tx_queue_limit); +/** + * fw_ver_show - display ME FW version + * + * @device: device pointer + * @attr: attribute pointer + * @buf: char out buffer + * + * Return: number of the bytes printed into buf or error + */ +static ssize_t fw_ver_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct mei_device *dev = dev_get_drvdata(device); + struct mei_fw_version *ver; + ssize_t cnt = 0; + int i; + + ver = dev->fw_ver; + + for (i = 0; i < MEI_MAX_FW_VER_BLOCKS; i++) + cnt += scnprintf(buf + cnt, PAGE_SIZE - cnt, "%u:%u.%u.%u.%u\n", + ver[i].platform, ver[i].major, ver[i].minor, + ver[i].hotfix, ver[i].buildno); + return cnt; +} +static DEVICE_ATTR_RO(fw_ver); + static struct attribute *mei_attrs[] = { &dev_attr_fw_status.attr, &dev_attr_hbm_ver.attr, &dev_attr_hbm_ver_drv.attr, &dev_attr_tx_queue_limit.attr, + &dev_attr_fw_ver.attr, NULL }; ATTRIBUTE_GROUPS(mei); diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index 4058ab5ad2d8..d522585b71b7 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -1,7 +1,7 @@ /* * * Intel Management Engine Interface (Intel MEI) Linux driver - * Copyright (c) 2003-2012, Intel Corporation. + * Copyright (c) 2003-2018, 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, @@ -354,6 +354,25 @@ enum mei_pg_state { const char *mei_pg_state_str(enum mei_pg_state state); +/** + * struct mei_fw_version - MEI FW version struct + * + * @platform: platform identifier + * @major: major version field + * @minor: minor version field + * @buildno: build number version field + * @hotfix: hotfix number version field + */ +struct mei_fw_version { + u8 platform; + u8 major; + u16 minor; + u16 buildno; + u16 hotfix; +}; + +#define MEI_MAX_FW_VER_BLOCKS 3 + /** * struct mei_device - MEI private device struct * @@ -402,6 +421,8 @@ const char *mei_pg_state_str(enum mei_pg_state state); * @hbm_f_ie_supported : hbm feature immediate reply to enum request * @hbm_f_os_supported : hbm feature support OS ver message * + * @fw_ver : FW versions + * * @me_clients_rwsem: rw lock over me_clients list * @me_clients : list of FW clients * @me_clients_map : FW clients bit map @@ -478,6 +499,8 @@ struct mei_device { unsigned int hbm_f_ie_supported:1; unsigned int hbm_f_os_supported:1; + struct mei_fw_version fw_ver[MEI_MAX_FW_VER_BLOCKS]; + struct rw_semaphore me_clients_rwsem; struct list_head me_clients; DECLARE_BITMAP(me_clients_map, MEI_CLIENTS_MAX); -- cgit v1.2.3 From f294d00961d1d869ecffa60e280eeeee1ccf9a49 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 3 Jul 2018 12:05:47 +0200 Subject: misc: sram: fix resource leaks in probe error path Make sure to disable clocks and deregister any exported partitions before returning on late probe errors. Note that since commit ee895ccdf776 ("misc: sram: fix enabled clock leak on error path"), partitions are deliberately exported before enabling the clock so we stick to that logic here. A follow up patch will address this. Fixes: 2ae2e28852f2 ("misc: sram: add Atmel securam support") Cc: stable # 4.9 Cc: Alexandre Belloni Signed-off-by: Johan Hovold Signed-off-by: Greg Kroah-Hartman --- drivers/misc/sram.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/misc/sram.c b/drivers/misc/sram.c index c5dc6095686a..679647713e36 100644 --- a/drivers/misc/sram.c +++ b/drivers/misc/sram.c @@ -407,13 +407,20 @@ static int sram_probe(struct platform_device *pdev) if (init_func) { ret = init_func(); if (ret) - return ret; + goto err_disable_clk; } dev_dbg(sram->dev, "SRAM pool: %zu KiB @ 0x%p\n", gen_pool_size(sram->pool) / 1024, sram->virt_base); return 0; + +err_disable_clk: + if (sram->clk) + clk_disable_unprepare(sram->clk); + sram_free_partitions(sram); + + return ret; } static int sram_remove(struct platform_device *pdev) -- cgit v1.2.3 From d5b9653dd2bb7a2b1c8cc783c5d3b607bbb6b271 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 3 Jul 2018 12:05:48 +0200 Subject: misc: sram: enable clock before registering regions Make sure to enable the clock before registering regions and exporting partitions to user space at which point we must be prepared for I/O. Fixes: ee895ccdf776 ("misc: sram: fix enabled clock leak on error path") Signed-off-by: Johan Hovold Reviewed-by: Vladimir Zapolskiy Signed-off-by: Greg Kroah-Hartman --- drivers/misc/sram.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/drivers/misc/sram.c b/drivers/misc/sram.c index 679647713e36..74b183baf044 100644 --- a/drivers/misc/sram.c +++ b/drivers/misc/sram.c @@ -391,23 +391,23 @@ static int sram_probe(struct platform_device *pdev) if (IS_ERR(sram->pool)) return PTR_ERR(sram->pool); - ret = sram_reserve_regions(sram, res); - if (ret) - return ret; - sram->clk = devm_clk_get(sram->dev, NULL); if (IS_ERR(sram->clk)) sram->clk = NULL; else clk_prepare_enable(sram->clk); + ret = sram_reserve_regions(sram, res); + if (ret) + goto err_disable_clk; + platform_set_drvdata(pdev, sram); init_func = of_device_get_match_data(&pdev->dev); if (init_func) { ret = init_func(); if (ret) - goto err_disable_clk; + goto err_free_partitions; } dev_dbg(sram->dev, "SRAM pool: %zu KiB @ 0x%p\n", @@ -415,10 +415,11 @@ static int sram_probe(struct platform_device *pdev) return 0; +err_free_partitions: + sram_free_partitions(sram); err_disable_clk: if (sram->clk) clk_disable_unprepare(sram->clk); - sram_free_partitions(sram); return ret; } -- cgit v1.2.3 From 36cb055f1bcfd10e4565867a696ff688d9de3cf4 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Wed, 4 Jul 2018 08:46:26 +0200 Subject: dt-bindings: vendor-prefixes: fix bad merge Remove the vendor-prefix reject file which was accidentally added when merging the gnss sirfstar binding. The wi2wi prefix had already been added by commit a593bff82cae2 ("dt-bindings: define vendor prefix for Wi2Wi, Inc."). Fixes: 176193b7dd6e ("dt-bindings: gnss: add sirfstar binding") Reported-by: Andrew Morton Signed-off-by: Johan Hovold Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/vendor-prefixes.txt.rej | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 Documentation/devicetree/bindings/vendor-prefixes.txt.rej diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt.rej b/Documentation/devicetree/bindings/vendor-prefixes.txt.rej deleted file mode 100644 index a32acd9ce2b2..000000000000 --- a/Documentation/devicetree/bindings/vendor-prefixes.txt.rej +++ /dev/null @@ -1,10 +0,0 @@ ---- Documentation/devicetree/bindings/vendor-prefixes.txt -+++ Documentation/devicetree/bindings/vendor-prefixes.txt -@@ -395,6 +397,7 @@ vot Vision Optical Technology Co., Ltd. - wd Western Digital Corp. - wetek WeTek Electronics, limited. - wexler Wexler -+wi2wi Wi2Wi, Inc. - winbond Winbond Electronics corp. - winstar Winstar Display Corp. - wlf Wolfson Microelectronics -- cgit v1.2.3 From a59f95c7a2c35031264eafc62e684df07f5f2a8f Mon Sep 17 00:00:00 2001 From: Alan Tull Date: Wed, 27 Jun 2018 20:56:19 -0500 Subject: Documentation: fpga: cleanup Minor fixes including: * fix some typos * correct use of a/an * rephrase explanation of .state ops function * s/re-use/reuse/ (use only one spelling of 'reuse' in these docs) * s/cpu/CPU/ Signed-off-by: Alan Tull Acked-by: Randy Dunlap Signed-off-by: Greg Kroah-Hartman --- Documentation/driver-api/fpga/fpga-mgr.rst | 12 ++++++------ Documentation/driver-api/fpga/fpga-region.rst | 12 ++++++------ Documentation/driver-api/fpga/intro.rst | 14 +++++++------- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/Documentation/driver-api/fpga/fpga-mgr.rst b/Documentation/driver-api/fpga/fpga-mgr.rst index bcf2dd24e179..4b3825da48d9 100644 --- a/Documentation/driver-api/fpga/fpga-mgr.rst +++ b/Documentation/driver-api/fpga/fpga-mgr.rst @@ -83,7 +83,7 @@ The programming sequence is:: 3. .write_complete The .write_init function will prepare the FPGA to receive the image data. The -buffer passed into .write_init will be atmost .initial_header_size bytes long, +buffer passed into .write_init will be at most .initial_header_size bytes long; if the whole bitstream is not immediately available then the core code will buffer up at least this much before starting. @@ -98,9 +98,9 @@ scatter list. This interface is suitable for drivers which use DMA. The .write_complete function is called after all the image has been written to put the FPGA into operating mode. -The ops include a .state function which will read the hardware FPGA manager and -return a code of type enum fpga_mgr_states. It doesn't result in a change in -hardware state. +The ops include a .state function which will determine the state the FPGA is in +and return a code of type enum fpga_mgr_states. It doesn't result in a change +in state. How to write an image buffer to a supported FPGA ------------------------------------------------ @@ -181,8 +181,8 @@ API for implementing a new FPGA Manager driver .. kernel-doc:: drivers/fpga/fpga-mgr.c :functions: fpga_mgr_unregister -API for programming a FPGA --------------------------- +API for programming an FPGA +--------------------------- .. kernel-doc:: include/linux/fpga/fpga-mgr.h :functions: fpga_image_info diff --git a/Documentation/driver-api/fpga/fpga-region.rst b/Documentation/driver-api/fpga/fpga-region.rst index f89e4a311722..f30333ce828e 100644 --- a/Documentation/driver-api/fpga/fpga-region.rst +++ b/Documentation/driver-api/fpga/fpga-region.rst @@ -4,7 +4,7 @@ FPGA Region Overview -------- -This document is meant to be an brief overview of the FPGA region API usage. A +This document is meant to be a brief overview of the FPGA region API usage. A more conceptual look at regions can be found in the Device Tree binding document [#f1]_. @@ -31,11 +31,11 @@ fpga_image_info including: * pointers to the image as either a scatter-gather buffer, a contiguous buffer, or the name of firmware file - * flags indicating specifics such as whether the image if for partial + * flags indicating specifics such as whether the image is for partial reconfiguration. -How to program a FPGA using a region ------------------------------------- +How to program an FPGA using a region +------------------------------------- First, allocate the info struct:: @@ -77,8 +77,8 @@ An example of usage can be seen in the probe function of [#f2]_. .. [#f1] ../devicetree/bindings/fpga/fpga-region.txt .. [#f2] ../../drivers/fpga/of-fpga-region.c -API to program a FGPA ---------------------- +API to program an FPGA +---------------------- .. kernel-doc:: drivers/fpga/fpga-region.c :functions: fpga_region_program_fpga diff --git a/Documentation/driver-api/fpga/intro.rst b/Documentation/driver-api/fpga/intro.rst index 51cd81dbb4dc..50d1cab84950 100644 --- a/Documentation/driver-api/fpga/intro.rst +++ b/Documentation/driver-api/fpga/intro.rst @@ -12,18 +12,18 @@ Linux. Some of the core intentions of the FPGA subsystems are: * Code should not be shared between upper and lower layers. This should go without saying. If that seems necessary, there's probably - framework functionality that that can be added that will benefit + framework functionality that can be added that will benefit other users. Write the linux-fpga mailing list and maintainers and seek out a solution that expands the framework for broad reuse. -* Generally, when adding code, think of the future. Plan for re-use. +* Generally, when adding code, think of the future. Plan for reuse. The framework in the kernel is divided into: FPGA Manager ------------ -If you are adding a new FPGA or a new method of programming a FPGA, +If you are adding a new FPGA or a new method of programming an FPGA, this is the subsystem for you. Low level FPGA manager drivers contain the knowledge of how to program a specific device. This subsystem includes the framework in fpga-mgr.c and the low level drivers that @@ -32,10 +32,10 @@ are registered with it. FPGA Bridge ----------- -FPGA Bridges prevent spurious signals from going out of a FPGA or a -region of a FPGA during programming. They are disabled before +FPGA Bridges prevent spurious signals from going out of an FPGA or a +region of an FPGA during programming. They are disabled before programming begins and re-enabled afterwards. An FPGA bridge may be -actual hard hardware that gates a bus to a cpu or a soft ("freeze") +actual hard hardware that gates a bus to a CPU or a soft ("freeze") bridge in FPGA fabric that surrounds a partial reconfiguration region of an FPGA. This subsystem includes fpga-bridge.c and the low level drivers that are registered with it. @@ -44,7 +44,7 @@ FPGA Region ----------- If you are adding a new interface to the FPGA framework, add it on top -of a FPGA region to allow the most reuse of your interface. +of an FPGA region to allow the most reuse of your interface. The FPGA Region framework (fpga-region.c) associates managers and bridges as reconfigurable regions. A region may refer to the whole -- cgit v1.2.3 From 43986798fd504c8cd846e3c65bcf732e3f971938 Mon Sep 17 00:00:00 2001 From: Zhouyang Jia Date: Tue, 12 Jun 2018 11:07:21 +0800 Subject: ipack: add error handling for ioremap_nocache When ioremap_nocache fails, the lack of error-handling code may cause unexpected results. This patch adds error-handling code after calling ioremap_nocache. Signed-off-by: Zhouyang Jia Acked-by: Samuel Iglesias Gonsalvez Signed-off-by: Greg Kroah-Hartman --- drivers/ipack/carriers/tpci200.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/ipack/carriers/tpci200.c b/drivers/ipack/carriers/tpci200.c index a16b320739b4..8a9c169b6f99 100644 --- a/drivers/ipack/carriers/tpci200.c +++ b/drivers/ipack/carriers/tpci200.c @@ -304,6 +304,13 @@ static int tpci200_register(struct tpci200_board *tpci200) ioremap_nocache(pci_resource_start(tpci200->info->pdev, TPCI200_IP_INTERFACE_BAR), TPCI200_IFACE_SIZE); + if (!tpci200->info->interface_regs) { + dev_err(&tpci200->info->pdev->dev, + "(bn 0x%X, sn 0x%X) failed to map driver user space!", + tpci200->info->pdev->bus->number, + tpci200->info->pdev->devfn); + goto out_release_mem8_space; + } /* Initialize lock that protects interface_regs */ spin_lock_init(&tpci200->regs_lock); -- cgit v1.2.3 From 8134d27103b35dbdc94d762f82ca0bfb00f349ff Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Tue, 19 Jun 2018 16:12:02 +0100 Subject: slimbus: messaging: pass correct wbuf There seems to be a typo while filling msg for slim_write, wbuf is set to NULL instead of rbuf. Signed-off-by: Srinivas Kandagatla Signed-off-by: Greg Kroah-Hartman --- drivers/slimbus/messaging.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/slimbus/messaging.c b/drivers/slimbus/messaging.c index 457ea1f8db30..5a2ff01b394c 100644 --- a/drivers/slimbus/messaging.c +++ b/drivers/slimbus/messaging.c @@ -307,7 +307,7 @@ int slim_write(struct slim_device *sdev, u32 addr, size_t count, u8 *val) { struct slim_val_inf msg; - slim_fill_msg(&msg, addr, count, val, NULL); + slim_fill_msg(&msg, addr, count, NULL, val); return slim_xfer_msg(sdev, &msg, SLIM_MSG_MC_CHANGE_VALUE); } -- cgit v1.2.3 From 057ba872d0adc9b512e455f38159f1c3616b616c Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Tue, 19 Jun 2018 16:12:03 +0100 Subject: slimbus: messaging: remove multiple calls to pm_runtime_mark_last_busy There seems to be a multiple calls to pm_runtime_mark_last_busy(), which looks like a typo. Fix this by properly adding pm_runtime_put_autosuspend to put controller in auto suspend state. Signed-off-by: Srinivas Kandagatla Signed-off-by: Greg Kroah-Hartman --- drivers/slimbus/messaging.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/slimbus/messaging.c b/drivers/slimbus/messaging.c index 5a2ff01b394c..1c57b631031a 100644 --- a/drivers/slimbus/messaging.c +++ b/drivers/slimbus/messaging.c @@ -139,7 +139,7 @@ slim_xfer_err: * if there was error during this transaction */ pm_runtime_mark_last_busy(ctrl->dev); - pm_runtime_mark_last_busy(ctrl->dev); + pm_runtime_put_autosuspend(ctrl->dev); } return ret; } -- cgit v1.2.3 From ffa2d92191286f47b993c62013c351b397400708 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Tue, 19 Jun 2018 16:12:04 +0100 Subject: slimbus: messaging: initialize completion correctly slim_val_inf can contain random value from stack, make sure the completion is initialized to NULL while filling the msg. Signed-off-by: Srinivas Kandagatla Signed-off-by: Greg Kroah-Hartman --- drivers/slimbus/messaging.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/slimbus/messaging.c b/drivers/slimbus/messaging.c index 1c57b631031a..e3605ed1c459 100644 --- a/drivers/slimbus/messaging.c +++ b/drivers/slimbus/messaging.c @@ -246,6 +246,7 @@ static void slim_fill_msg(struct slim_val_inf *msg, u32 addr, msg->num_bytes = count; msg->rbuf = rbuf; msg->wbuf = wbuf; + msg->comp = NULL; } /** -- cgit v1.2.3 From ba883d90b1c8eccf8e315bd2a79ce6b0b2420d3b Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Tue, 19 Jun 2018 16:12:05 +0100 Subject: slimbus: qcom: remove redundant depends in Kconfig QCOM SLIMBus controller is already under a 'if SLIMBUS' in Kconfig, having depends on SLIMBUS is totally redundant. Just remove it. Signed-off-by: Srinivas Kandagatla Signed-off-by: Greg Kroah-Hartman --- drivers/slimbus/Kconfig | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/slimbus/Kconfig b/drivers/slimbus/Kconfig index 1a632fad597e..bfb78d9a6583 100644 --- a/drivers/slimbus/Kconfig +++ b/drivers/slimbus/Kconfig @@ -15,7 +15,6 @@ if SLIMBUS # SLIMbus controllers config SLIM_QCOM_CTRL tristate "Qualcomm SLIMbus Manager Component" - depends on SLIMBUS depends on HAS_IOMEM help Select driver if Qualcomm's SLIMbus Manager Component is -- cgit v1.2.3 From e0772de8a48b69d39624cdf99fac8f4a3fcc387b Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Tue, 19 Jun 2018 17:12:57 +0100 Subject: slimbus: core: add of_slim_device_get() helper On SLIMBus controllers like Qcom NGD(non ported device), controller can request logical address once the remote side is powered, having a helper function like this to explicitly enumerate the bus is helpful. Also codec drivers which are taking to interface device would need such a helper too. Signed-off-by: Srinivas Kandagatla Signed-off-by: Greg Kroah-Hartman --- drivers/slimbus/core.c | 39 +++++++++++++++++++++++++++++++++++++++ include/linux/slimbus.h | 2 ++ 2 files changed, 41 insertions(+) diff --git a/drivers/slimbus/core.c b/drivers/slimbus/core.c index 7ddfc675b131..88248a4ecad9 100644 --- a/drivers/slimbus/core.c +++ b/drivers/slimbus/core.c @@ -356,6 +356,45 @@ struct slim_device *slim_get_device(struct slim_controller *ctrl, } EXPORT_SYMBOL_GPL(slim_get_device); +static int of_slim_match_dev(struct device *dev, void *data) +{ + struct device_node *np = data; + struct slim_device *sbdev = to_slim_device(dev); + + return (sbdev->dev.of_node == np); +} + +static struct slim_device *of_find_slim_device(struct slim_controller *ctrl, + struct device_node *np) +{ + struct slim_device *sbdev; + struct device *dev; + + dev = device_find_child(ctrl->dev, np, of_slim_match_dev); + if (dev) { + sbdev = to_slim_device(dev); + return sbdev; + } + + return NULL; +} + +/** + * of_slim_get_device() - get handle to a device using dt node. + * + * @ctrl: Controller on which this device will be added/queried + * @np: node pointer to device + * + * Return: pointer to a device if it has already reported. Creates a new + * device and returns pointer to it if the device has not yet enumerated. + */ +struct slim_device *of_slim_get_device(struct slim_controller *ctrl, + struct device_node *np) +{ + return of_find_slim_device(ctrl, np); +} +EXPORT_SYMBOL_GPL(of_slim_get_device); + static int slim_device_alloc_laddr(struct slim_device *sbdev, bool report_present) { diff --git a/include/linux/slimbus.h b/include/linux/slimbus.h index c36cf121d2cd..efa36a852dc3 100644 --- a/include/linux/slimbus.h +++ b/include/linux/slimbus.h @@ -138,6 +138,8 @@ static inline void slim_set_devicedata(struct slim_device *dev, void *data) dev_set_drvdata(&dev->dev, data); } +struct slim_device *of_slim_get_device(struct slim_controller *ctrl, + struct device_node *np); struct slim_device *slim_get_device(struct slim_controller *ctrl, struct slim_eaddr *e_addr); int slim_get_logical_addr(struct slim_device *sbdev); -- cgit v1.2.3 From db455d66b9900a8a81fe474f11c78c8c8a95eefa Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Tue, 19 Jun 2018 17:12:58 +0100 Subject: slimbus: core: rearrange slim_eaddr structure Rearrange struct slim_eaddr so that the structure is packed correctly to be able to send in SLIMBus messages. Signed-off-by: Srinivas Kandagatla Signed-off-by: Greg Kroah-Hartman --- include/linux/slimbus.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/include/linux/slimbus.h b/include/linux/slimbus.h index efa36a852dc3..63801bcc5e60 100644 --- a/include/linux/slimbus.h +++ b/include/linux/slimbus.h @@ -14,16 +14,16 @@ extern struct bus_type slimbus_bus; /** * struct slim_eaddr - Enumeration address for a SLIMbus device - * @manf_id: Manufacturer Id for the device - * @prod_code: Product code - * @dev_index: Device index * @instance: Instance value + * @dev_index: Device index + * @prod_code: Product code + * @manf_id: Manufacturer Id for the device */ struct slim_eaddr { - u16 manf_id; - u16 prod_code; - u8 dev_index; u8 instance; + u8 dev_index; + u16 prod_code; + u16 manf_id; } __packed; /** -- cgit v1.2.3 From d3062a21093093bc77a8ebf1b555e380993993e7 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Tue, 19 Jun 2018 17:12:59 +0100 Subject: slimbus: messaging: add slim_alloc/free_txn_tid() This patch adds slim_alloc_txn_tid() and slim_free_txn_tid() api to allow controllers like ngd to allocate tids for user specific commands. This also cleans up the existing code to use single place for tid allocations and free. This patch also make the tid allocation cyclic one, its very useful to track the transactions back during debug. Signed-off-by: Srinivas Kandagatla Signed-off-by: Greg Kroah-Hartman --- drivers/slimbus/messaging.c | 74 +++++++++++++++++++++++++++++++-------------- drivers/slimbus/slimbus.h | 2 ++ 2 files changed, 54 insertions(+), 22 deletions(-) diff --git a/drivers/slimbus/messaging.c b/drivers/slimbus/messaging.c index e3605ed1c459..d5879142dbef 100644 --- a/drivers/slimbus/messaging.c +++ b/drivers/slimbus/messaging.c @@ -29,22 +29,19 @@ void slim_msg_response(struct slim_controller *ctrl, u8 *reply, u8 tid, u8 len) spin_lock_irqsave(&ctrl->txn_lock, flags); txn = idr_find(&ctrl->tid_idr, tid); - if (txn == NULL) { - spin_unlock_irqrestore(&ctrl->txn_lock, flags); + spin_unlock_irqrestore(&ctrl->txn_lock, flags); + + if (txn == NULL) return; - } msg = txn->msg; if (msg == NULL || msg->rbuf == NULL) { dev_err(ctrl->dev, "Got response to invalid TID:%d, len:%d\n", tid, len); - spin_unlock_irqrestore(&ctrl->txn_lock, flags); return; } - idr_remove(&ctrl->tid_idr, tid); - spin_unlock_irqrestore(&ctrl->txn_lock, flags); - + slim_free_txn_tid(ctrl, txn); memcpy(msg->rbuf, reply, len); if (txn->comp) complete(txn->comp); @@ -55,6 +52,48 @@ void slim_msg_response(struct slim_controller *ctrl, u8 *reply, u8 tid, u8 len) } EXPORT_SYMBOL_GPL(slim_msg_response); +/** + * slim_alloc_txn_tid() - Allocate a tid to txn + * + * @ctrl: Controller handle + * @txn: transaction to be allocated with tid. + * + * Return: zero on success with valid txn->tid and error code on failures. + */ +int slim_alloc_txn_tid(struct slim_controller *ctrl, struct slim_msg_txn *txn) +{ + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&ctrl->txn_lock, flags); + ret = idr_alloc_cyclic(&ctrl->tid_idr, txn, 0, + SLIM_MAX_TIDS, GFP_ATOMIC); + if (ret < 0) { + spin_unlock_irqrestore(&ctrl->txn_lock, flags); + return ret; + } + txn->tid = ret; + spin_unlock_irqrestore(&ctrl->txn_lock, flags); + return 0; +} +EXPORT_SYMBOL_GPL(slim_alloc_txn_tid); + +/** + * slim_free_txn_tid() - Freee tid of txn + * + * @ctrl: Controller handle + * @txn: transaction whose tid should be freed + */ +void slim_free_txn_tid(struct slim_controller *ctrl, struct slim_msg_txn *txn) +{ + unsigned long flags; + + spin_lock_irqsave(&ctrl->txn_lock, flags); + idr_remove(&ctrl->tid_idr, txn->tid); + spin_unlock_irqrestore(&ctrl->txn_lock, flags); +} +EXPORT_SYMBOL_GPL(slim_free_txn_tid); + /** * slim_do_transfer() - Process a SLIMbus-messaging transaction * @@ -72,8 +111,7 @@ int slim_do_transfer(struct slim_controller *ctrl, struct slim_msg_txn *txn) { DECLARE_COMPLETION_ONSTACK(done); bool need_tid = false, clk_pause_msg = false; - unsigned long flags; - int ret, tid, timeout; + int ret, timeout; /* * do not vote for runtime-PM if the transactions are part of clock @@ -97,34 +135,26 @@ int slim_do_transfer(struct slim_controller *ctrl, struct slim_msg_txn *txn) need_tid = slim_tid_txn(txn->mt, txn->mc); if (need_tid) { - spin_lock_irqsave(&ctrl->txn_lock, flags); - tid = idr_alloc(&ctrl->tid_idr, txn, 0, - SLIM_MAX_TIDS, GFP_ATOMIC); - txn->tid = tid; + ret = slim_alloc_txn_tid(ctrl, txn); + if (ret) + return ret; if (!txn->msg->comp) txn->comp = &done; else txn->comp = txn->comp; - - spin_unlock_irqrestore(&ctrl->txn_lock, flags); - - if (tid < 0) - return tid; } ret = ctrl->xfer_msg(ctrl, txn); - if (ret && need_tid && !txn->msg->comp) { + if (!ret && need_tid && !txn->msg->comp) { unsigned long ms = txn->rl + HZ; timeout = wait_for_completion_timeout(txn->comp, msecs_to_jiffies(ms)); if (!timeout) { ret = -ETIMEDOUT; - spin_lock_irqsave(&ctrl->txn_lock, flags); - idr_remove(&ctrl->tid_idr, tid); - spin_unlock_irqrestore(&ctrl->txn_lock, flags); + slim_free_txn_tid(ctrl, txn); } } diff --git a/drivers/slimbus/slimbus.h b/drivers/slimbus/slimbus.h index 79f8e05d92dd..2ba6545fa716 100644 --- a/drivers/slimbus/slimbus.h +++ b/drivers/slimbus/slimbus.h @@ -240,6 +240,8 @@ int slim_unregister_controller(struct slim_controller *ctrl); void slim_msg_response(struct slim_controller *ctrl, u8 *reply, u8 tid, u8 l); int slim_do_transfer(struct slim_controller *ctrl, struct slim_msg_txn *txn); int slim_ctrl_clk_pause(struct slim_controller *ctrl, bool wakeup, u8 restart); +int slim_alloc_txn_tid(struct slim_controller *ctrl, struct slim_msg_txn *txn); +void slim_free_txn_tid(struct slim_controller *ctrl, struct slim_msg_txn *txn); static inline bool slim_tid_txn(u8 mt, u8 mc) { -- cgit v1.2.3 From 992d36158a06322638fc6041744886766336fe50 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Tue, 19 Jun 2018 17:13:00 +0100 Subject: slimbus: ngd: dt-bindings: Add slim ngd dt bindings This patch adds bindings for Qualcomm SLIMBus NGD controller. SLIMBus NGD controller is a light-weight driver responsible for communicating with SLIMBus slaves directly over the bus using messaging interface and communicating with master component residing on ADSP for bandwidth and data-channel management Signed-off-by: Srinivas Kandagatla Reviewed-by: Rob Herring Tested-by: Craig Tatlor Reviewed-by: Vinod Koul Signed-off-by: Greg Kroah-Hartman --- .../bindings/slimbus/slim-ngd-qcom-ctrl.txt | 84 ++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 Documentation/devicetree/bindings/slimbus/slim-ngd-qcom-ctrl.txt diff --git a/Documentation/devicetree/bindings/slimbus/slim-ngd-qcom-ctrl.txt b/Documentation/devicetree/bindings/slimbus/slim-ngd-qcom-ctrl.txt new file mode 100644 index 000000000000..e94a2ad3a710 --- /dev/null +++ b/Documentation/devicetree/bindings/slimbus/slim-ngd-qcom-ctrl.txt @@ -0,0 +1,84 @@ +Qualcomm SLIMBus Non Generic Device (NGD) Controller binding + +SLIMBus NGD controller is a light-weight driver responsible for communicating +with SLIMBus slaves directly over the bus using messaging interface and +communicating with master component residing on ADSP for bandwidth and +data-channel management + +Please refer to slimbus/bus.txt for details of the common SLIMBus bindings. + +- compatible: + Usage: required + Value type: + Definition: must be "qcom,slim-ngd-v.." + must be one of the following. + "qcom,slim-ngd-v1.5.0" for MSM8996 + "qcom,slim-ngd-v2.1.0" for SDM845 + +- reg: + Usage: required + Value type: + Definition: must specify the base address and size of the controller + register space. +- dmas + Usage: required + Value type: + Definition: List of rx and tx dma channels + +- dma-names + Usage: required + Value type: + Definition: must be "rx" and "tx". + +- interrupts: + Usage: required + Value type: + Definition: must list controller IRQ. + +#address-cells + Usage: required + Value type: + Definition: Should be 1, reflecting the instance id of ngd. + +#size-cells + Usage: required + Value type: + Definition: Should be 0 + += NGD Devices +Each subnode represents an instance of NGD, must contain the following +properties: + +- reg: + Usage: required + Value type: + Definition: Should be instance id of ngd. + +#address-cells + Usage: required + Refer to slimbus/bus.txt for details of the common SLIMBus bindings. + +#size-cells + Usage: required + Refer to slimbus/bus.txt for details of the common SLIMBus bindings. + += EXAMPLE + +slim@91c0000 { + compatible = "qcom,slim-ngd-v1.5.0"; + reg = <0x91c0000 0x2c000>; + interrupts = <0 163 0>; + dmas = <&slimbam 3>, <&slimbam 4>; + dma-names = "rx", "tx"; + #address-cells = <1>; + #size-cells = <0>; + ngd@1 { + reg = <1>; + #address-cells = <1>; + #size-cells = <1>; + codec@1 { + compatible = "slim217,1a0"; + reg = <1 0>; + }; + }; +}; -- cgit v1.2.3 From 917809e2280bb83994be8b642373fd941d40c407 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Tue, 19 Jun 2018 17:13:01 +0100 Subject: slimbus: ngd: Add qcom SLIMBus NGD driver This patch adds suppor to Qualcomm SLIMBus Non-Generic Device (NGD) controller driver. This is light-weight SLIMBus controller driver responsible for communicating with slave HW directly over the bus using messaging interface, and communicating with master component residing on ADSP for bandwidth and data-channel management Based on intial work from Karthikeyan Ramasubramanian and Sagar Dharia Signed-off-by: Srinivas Kandagatla Tested-by: Craig Tatlor Reviewed-by: Vinod Koul Signed-off-by: Greg Kroah-Hartman --- drivers/slimbus/Kconfig | 11 + drivers/slimbus/Makefile | 3 + drivers/slimbus/qcom-ngd-ctrl.c | 1381 +++++++++++++++++++++++++++++++++++++++ drivers/slimbus/slimbus.h | 8 + 4 files changed, 1403 insertions(+) create mode 100644 drivers/slimbus/qcom-ngd-ctrl.c diff --git a/drivers/slimbus/Kconfig b/drivers/slimbus/Kconfig index bfb78d9a6583..9d73ad806698 100644 --- a/drivers/slimbus/Kconfig +++ b/drivers/slimbus/Kconfig @@ -20,4 +20,15 @@ config SLIM_QCOM_CTRL Select driver if Qualcomm's SLIMbus Manager Component is programmed using Linux kernel. +config SLIM_QCOM_NGD_CTRL + tristate "Qualcomm SLIMbus Satellite Non-Generic Device Component" + depends on QCOM_QMI_HELPERS + depends on HAS_IOMEM && DMA_ENGINE + help + Select driver if Qualcomm's SLIMbus Satellite Non-Generic Device + Component is programmed using Linux kernel. + This is light-weight slimbus controller driver responsible for + communicating with slave HW directly over the bus using messaging + interface, and communicating with master component residing on ADSP + for bandwidth and data-channel management. endif diff --git a/drivers/slimbus/Makefile b/drivers/slimbus/Makefile index a35a3da4eb78..c78c6e16c2df 100644 --- a/drivers/slimbus/Makefile +++ b/drivers/slimbus/Makefile @@ -8,3 +8,6 @@ slimbus-y := core.o messaging.o sched.o #Controllers obj-$(CONFIG_SLIM_QCOM_CTRL) += slim-qcom-ctrl.o slim-qcom-ctrl-y := qcom-ctrl.o + +obj-$(CONFIG_SLIM_QCOM_NGD_CTRL) += slim-qcom-ngd-ctrl.o +slim-qcom-ngd-ctrl-y := qcom-ngd-ctrl.o diff --git a/drivers/slimbus/qcom-ngd-ctrl.c b/drivers/slimbus/qcom-ngd-ctrl.c new file mode 100644 index 000000000000..8554e3f43522 --- /dev/null +++ b/drivers/slimbus/qcom-ngd-ctrl.c @@ -0,0 +1,1381 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2011-2017, The Linux Foundation. All rights reserved. +// Copyright (c) 2018, Linaro Limited + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "slimbus.h" + +/* NGD (Non-ported Generic Device) registers */ +#define NGD_CFG 0x0 +#define NGD_CFG_ENABLE BIT(0) +#define NGD_CFG_RX_MSGQ_EN BIT(1) +#define NGD_CFG_TX_MSGQ_EN BIT(2) +#define NGD_STATUS 0x4 +#define NGD_LADDR BIT(1) +#define NGD_RX_MSGQ_CFG 0x8 +#define NGD_INT_EN 0x10 +#define NGD_INT_RECFG_DONE BIT(24) +#define NGD_INT_TX_NACKED_2 BIT(25) +#define NGD_INT_MSG_BUF_CONTE BIT(26) +#define NGD_INT_MSG_TX_INVAL BIT(27) +#define NGD_INT_IE_VE_CHG BIT(28) +#define NGD_INT_DEV_ERR BIT(29) +#define NGD_INT_RX_MSG_RCVD BIT(30) +#define NGD_INT_TX_MSG_SENT BIT(31) +#define NGD_INT_STAT 0x14 +#define NGD_INT_CLR 0x18 +#define DEF_NGD_INT_MASK (NGD_INT_TX_NACKED_2 | NGD_INT_MSG_BUF_CONTE | \ + NGD_INT_MSG_TX_INVAL | NGD_INT_IE_VE_CHG | \ + NGD_INT_DEV_ERR | NGD_INT_TX_MSG_SENT | \ + NGD_INT_RX_MSG_RCVD) + +/* Slimbus QMI service */ +#define SLIMBUS_QMI_SVC_ID 0x0301 +#define SLIMBUS_QMI_SVC_V1 1 +#define SLIMBUS_QMI_INS_ID 0 +#define SLIMBUS_QMI_SELECT_INSTANCE_REQ_V01 0x0020 +#define SLIMBUS_QMI_SELECT_INSTANCE_RESP_V01 0x0020 +#define SLIMBUS_QMI_POWER_REQ_V01 0x0021 +#define SLIMBUS_QMI_POWER_RESP_V01 0x0021 +#define SLIMBUS_QMI_CHECK_FRAMER_STATUS_REQ 0x0022 +#define SLIMBUS_QMI_CHECK_FRAMER_STATUS_RESP 0x0022 +#define SLIMBUS_QMI_POWER_REQ_MAX_MSG_LEN 14 +#define SLIMBUS_QMI_POWER_RESP_MAX_MSG_LEN 7 +#define SLIMBUS_QMI_SELECT_INSTANCE_REQ_MAX_MSG_LEN 14 +#define SLIMBUS_QMI_SELECT_INSTANCE_RESP_MAX_MSG_LEN 7 +#define SLIMBUS_QMI_CHECK_FRAMER_STAT_RESP_MAX_MSG_LEN 7 +/* QMI response timeout of 500ms */ +#define SLIMBUS_QMI_RESP_TOUT 1000 + +/* User defined commands */ +#define SLIM_USR_MC_GENERIC_ACK 0x25 +#define SLIM_USR_MC_MASTER_CAPABILITY 0x0 +#define SLIM_USR_MC_REPORT_SATELLITE 0x1 +#define SLIM_USR_MC_ADDR_QUERY 0xD +#define SLIM_USR_MC_ADDR_REPLY 0xE +#define SLIM_USR_MC_DEFINE_CHAN 0x20 +#define SLIM_USR_MC_DEF_ACT_CHAN 0x21 +#define SLIM_USR_MC_CHAN_CTRL 0x23 +#define SLIM_USR_MC_RECONFIG_NOW 0x24 +#define SLIM_USR_MC_REQ_BW 0x28 +#define SLIM_USR_MC_CONNECT_SRC 0x2C +#define SLIM_USR_MC_CONNECT_SINK 0x2D +#define SLIM_USR_MC_DISCONNECT_PORT 0x2E +#define SLIM_USR_MC_REPEAT_CHANGE_VALUE 0x0 + +#define QCOM_SLIM_NGD_AUTOSUSPEND MSEC_PER_SEC +#define SLIM_RX_MSGQ_TIMEOUT_VAL 0x10000 + +#define SLIM_LA_MGR 0xFF +#define SLIM_ROOT_FREQ 24576000 +#define LADDR_RETRY 5 + +/* Per spec.max 40 bytes per received message */ +#define SLIM_MSGQ_BUF_LEN 40 +#define QCOM_SLIM_NGD_DESC_NUM 32 + +#define SLIM_MSG_ASM_FIRST_WORD(l, mt, mc, dt, ad) \ + ((l) | ((mt) << 5) | ((mc) << 8) | ((dt) << 15) | ((ad) << 16)) + +#define INIT_MX_RETRIES 10 +#define DEF_RETRY_MS 10 +#define SAT_MAGIC_LSB 0xD9 +#define SAT_MAGIC_MSB 0xC5 +#define SAT_MSG_VER 0x1 +#define SAT_MSG_PROT 0x1 +#define to_ngd(d) container_of(d, struct qcom_slim_ngd, dev) + +struct ngd_reg_offset_data { + u32 offset, size; +}; + +static const struct ngd_reg_offset_data ngd_v1_5_offset_info = { + .offset = 0x1000, + .size = 0x1000, +}; + +enum qcom_slim_ngd_state { + QCOM_SLIM_NGD_CTRL_AWAKE, + QCOM_SLIM_NGD_CTRL_IDLE, + QCOM_SLIM_NGD_CTRL_ASLEEP, + QCOM_SLIM_NGD_CTRL_DOWN, +}; + +struct qcom_slim_ngd_qmi { + struct qmi_handle qmi; + struct sockaddr_qrtr svc_info; + struct qmi_handle svc_event_hdl; + struct qmi_response_type_v01 resp; + struct qmi_handle *handle; + struct completion qmi_comp; +}; + +struct qcom_slim_ngd_ctrl; +struct qcom_slim_ngd; + +struct qcom_slim_ngd_dma_desc { + struct dma_async_tx_descriptor *desc; + struct qcom_slim_ngd_ctrl *ctrl; + struct completion *comp; + dma_cookie_t cookie; + dma_addr_t phys; + void *base; +}; + +struct qcom_slim_ngd { + struct platform_device *pdev; + void __iomem *base; + int id; +}; + +struct qcom_slim_ngd_ctrl { + struct slim_framer framer; + struct slim_controller ctrl; + struct qcom_slim_ngd_qmi qmi; + struct qcom_slim_ngd *ngd; + struct device *dev; + void __iomem *base; + struct dma_chan *dma_rx_channel; + struct dma_chan *dma_tx_channel; + struct qcom_slim_ngd_dma_desc rx_desc[QCOM_SLIM_NGD_DESC_NUM]; + struct qcom_slim_ngd_dma_desc txdesc[QCOM_SLIM_NGD_DESC_NUM]; + struct completion reconf; + struct work_struct m_work; + struct workqueue_struct *mwq; + spinlock_t tx_buf_lock; + enum qcom_slim_ngd_state state; + dma_addr_t rx_phys_base; + dma_addr_t tx_phys_base; + void *rx_base; + void *tx_base; + int tx_tail; + int tx_head; + u32 ver; +}; + +enum slimbus_mode_enum_type_v01 { + /* To force a 32 bit signed enum. Do not change or use*/ + SLIMBUS_MODE_ENUM_TYPE_MIN_ENUM_VAL_V01 = INT_MIN, + SLIMBUS_MODE_SATELLITE_V01 = 1, + SLIMBUS_MODE_MASTER_V01 = 2, + SLIMBUS_MODE_ENUM_TYPE_MAX_ENUM_VAL_V01 = INT_MAX, +}; + +enum slimbus_pm_enum_type_v01 { + /* To force a 32 bit signed enum. Do not change or use*/ + SLIMBUS_PM_ENUM_TYPE_MIN_ENUM_VAL_V01 = INT_MIN, + SLIMBUS_PM_INACTIVE_V01 = 1, + SLIMBUS_PM_ACTIVE_V01 = 2, + SLIMBUS_PM_ENUM_TYPE_MAX_ENUM_VAL_V01 = INT_MAX, +}; + +enum slimbus_resp_enum_type_v01 { + SLIMBUS_RESP_ENUM_TYPE_MIN_VAL_V01 = INT_MIN, + SLIMBUS_RESP_SYNCHRONOUS_V01 = 1, + SLIMBUS_RESP_ENUM_TYPE_MAX_VAL_V01 = INT_MAX, +}; + +struct slimbus_select_inst_req_msg_v01 { + uint32_t instance; + uint8_t mode_valid; + enum slimbus_mode_enum_type_v01 mode; +}; + +struct slimbus_select_inst_resp_msg_v01 { + struct qmi_response_type_v01 resp; +}; + +struct slimbus_power_req_msg_v01 { + enum slimbus_pm_enum_type_v01 pm_req; + uint8_t resp_type_valid; + enum slimbus_resp_enum_type_v01 resp_type; +}; + +struct slimbus_power_resp_msg_v01 { + struct qmi_response_type_v01 resp; +}; + +static struct qmi_elem_info slimbus_select_inst_req_msg_v01_ei[] = { + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(uint32_t), + .array_type = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct slimbus_select_inst_req_msg_v01, + instance), + .ei_array = NULL, + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .array_type = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct slimbus_select_inst_req_msg_v01, + mode_valid), + .ei_array = NULL, + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(enum slimbus_mode_enum_type_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct slimbus_select_inst_req_msg_v01, + mode), + .ei_array = NULL, + }, + { + .data_type = QMI_EOTI, + .elem_len = 0, + .elem_size = 0, + .array_type = NO_ARRAY, + .tlv_type = 0x00, + .offset = 0, + .ei_array = NULL, + }, +}; + +static struct qmi_elem_info slimbus_select_inst_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct slimbus_select_inst_resp_msg_v01, + resp), + .ei_array = qmi_response_type_v01_ei, + }, + { + .data_type = QMI_EOTI, + .elem_len = 0, + .elem_size = 0, + .array_type = NO_ARRAY, + .tlv_type = 0x00, + .offset = 0, + .ei_array = NULL, + }, +}; + +static struct qmi_elem_info slimbus_power_req_msg_v01_ei[] = { + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(enum slimbus_pm_enum_type_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct slimbus_power_req_msg_v01, + pm_req), + .ei_array = NULL, + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .array_type = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct slimbus_power_req_msg_v01, + resp_type_valid), + }, + { + .data_type = QMI_SIGNED_4_BYTE_ENUM, + .elem_len = 1, + .elem_size = sizeof(enum slimbus_resp_enum_type_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct slimbus_power_req_msg_v01, + resp_type), + }, + { + .data_type = QMI_EOTI, + .elem_len = 0, + .elem_size = 0, + .array_type = NO_ARRAY, + .tlv_type = 0x00, + .offset = 0, + .ei_array = NULL, + }, +}; + +static struct qmi_elem_info slimbus_power_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct slimbus_power_resp_msg_v01, resp), + .ei_array = qmi_response_type_v01_ei, + }, + { + .data_type = QMI_EOTI, + .elem_len = 0, + .elem_size = 0, + .array_type = NO_ARRAY, + .tlv_type = 0x00, + .offset = 0, + .ei_array = NULL, + }, +}; + +static int qcom_slim_qmi_send_select_inst_req(struct qcom_slim_ngd_ctrl *ctrl, + struct slimbus_select_inst_req_msg_v01 *req) +{ + struct slimbus_select_inst_resp_msg_v01 resp = { { 0, 0 } }; + struct qmi_txn txn; + int rc; + + rc = qmi_txn_init(ctrl->qmi.handle, &txn, + slimbus_select_inst_resp_msg_v01_ei, &resp); + if (rc < 0) { + dev_err(ctrl->dev, "QMI TXN init fail: %d\n", rc); + return rc; + } + + rc = qmi_send_request(ctrl->qmi.handle, NULL, &txn, + SLIMBUS_QMI_SELECT_INSTANCE_REQ_V01, + SLIMBUS_QMI_SELECT_INSTANCE_REQ_MAX_MSG_LEN, + slimbus_select_inst_req_msg_v01_ei, req); + if (rc < 0) { + dev_err(ctrl->dev, "QMI send req fail %d\n", rc); + qmi_txn_cancel(&txn); + return rc; + } + + rc = qmi_txn_wait(&txn, SLIMBUS_QMI_RESP_TOUT); + if (rc < 0) { + dev_err(ctrl->dev, "QMI TXN wait fail: %d\n", rc); + return rc; + } + /* Check the response */ + if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { + dev_err(ctrl->dev, "QMI request failed 0x%x\n", + resp.resp.result); + return -EREMOTEIO; + } + + return 0; +} + +static void qcom_slim_qmi_power_resp_cb(struct qmi_handle *handle, + struct sockaddr_qrtr *sq, + struct qmi_txn *txn, const void *data) +{ + struct slimbus_power_resp_msg_v01 *resp; + + resp = (struct slimbus_power_resp_msg_v01 *)data; + if (resp->resp.result != QMI_RESULT_SUCCESS_V01) + pr_err("QMI power request failed 0x%x\n", + resp->resp.result); + + complete(&txn->completion); +} + +static int qcom_slim_qmi_send_power_request(struct qcom_slim_ngd_ctrl *ctrl, + struct slimbus_power_req_msg_v01 *req) +{ + struct slimbus_power_resp_msg_v01 resp = { { 0, 0 } }; + struct qmi_txn txn; + int rc; + + rc = qmi_txn_init(ctrl->qmi.handle, &txn, + slimbus_power_resp_msg_v01_ei, &resp); + + rc = qmi_send_request(ctrl->qmi.handle, NULL, &txn, + SLIMBUS_QMI_POWER_REQ_V01, + SLIMBUS_QMI_POWER_REQ_MAX_MSG_LEN, + slimbus_power_req_msg_v01_ei, req); + if (rc < 0) { + dev_err(ctrl->dev, "QMI send req fail %d\n", rc); + qmi_txn_cancel(&txn); + return rc; + } + + rc = qmi_txn_wait(&txn, SLIMBUS_QMI_RESP_TOUT); + if (rc < 0) { + dev_err(ctrl->dev, "QMI TXN wait fail: %d\n", rc); + return rc; + } + + /* Check the response */ + if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { + dev_err(ctrl->dev, "QMI request failed 0x%x\n", + resp.resp.result); + return -EREMOTEIO; + } + + return 0; +} + +static struct qmi_msg_handler qcom_slim_qmi_msg_handlers[] = { + { + .type = QMI_RESPONSE, + .msg_id = SLIMBUS_QMI_POWER_RESP_V01, + .ei = slimbus_power_resp_msg_v01_ei, + .decoded_size = sizeof(struct slimbus_power_resp_msg_v01), + .fn = qcom_slim_qmi_power_resp_cb, + }, + {} +}; + +static int qcom_slim_qmi_init(struct qcom_slim_ngd_ctrl *ctrl, + bool apps_is_master) +{ + struct slimbus_select_inst_req_msg_v01 req; + struct qmi_handle *handle; + int rc; + + handle = devm_kzalloc(ctrl->dev, sizeof(*handle), GFP_KERNEL); + if (!handle) + return -ENOMEM; + + rc = qmi_handle_init(handle, SLIMBUS_QMI_POWER_REQ_MAX_MSG_LEN, + NULL, qcom_slim_qmi_msg_handlers); + if (rc < 0) { + dev_err(ctrl->dev, "QMI client init failed: %d\n", rc); + goto qmi_handle_init_failed; + } + + rc = kernel_connect(handle->sock, + (struct sockaddr *)&ctrl->qmi.svc_info, + sizeof(ctrl->qmi.svc_info), 0); + if (rc < 0) { + dev_err(ctrl->dev, "Remote Service connect failed: %d\n", rc); + goto qmi_connect_to_service_failed; + } + + /* Instance is 0 based */ + req.instance = (ctrl->ngd->id >> 1); + req.mode_valid = 1; + + /* Mode indicates the role of the ADSP */ + if (apps_is_master) + req.mode = SLIMBUS_MODE_SATELLITE_V01; + else + req.mode = SLIMBUS_MODE_MASTER_V01; + + ctrl->qmi.handle = handle; + + rc = qcom_slim_qmi_send_select_inst_req(ctrl, &req); + if (rc) { + dev_err(ctrl->dev, "failed to select h/w instance\n"); + goto qmi_select_instance_failed; + } + + return 0; + +qmi_select_instance_failed: + ctrl->qmi.handle = NULL; +qmi_connect_to_service_failed: + qmi_handle_release(handle); +qmi_handle_init_failed: + devm_kfree(ctrl->dev, handle); + return rc; +} + +static void qcom_slim_qmi_exit(struct qcom_slim_ngd_ctrl *ctrl) +{ + if (!ctrl->qmi.handle) + return; + + qmi_handle_release(ctrl->qmi.handle); + devm_kfree(ctrl->dev, ctrl->qmi.handle); + ctrl->qmi.handle = NULL; +} + +static int qcom_slim_qmi_power_request(struct qcom_slim_ngd_ctrl *ctrl, + bool active) +{ + struct slimbus_power_req_msg_v01 req; + + if (active) + req.pm_req = SLIMBUS_PM_ACTIVE_V01; + else + req.pm_req = SLIMBUS_PM_INACTIVE_V01; + + req.resp_type_valid = 0; + + return qcom_slim_qmi_send_power_request(ctrl, &req); +} + +static u32 *qcom_slim_ngd_tx_msg_get(struct qcom_slim_ngd_ctrl *ctrl, int len, + struct completion *comp) +{ + struct qcom_slim_ngd_dma_desc *desc; + unsigned long flags; + + spin_lock_irqsave(&ctrl->tx_buf_lock, flags); + + if ((ctrl->tx_tail + 1) % QCOM_SLIM_NGD_DESC_NUM == ctrl->tx_head) { + spin_unlock_irqrestore(&ctrl->tx_buf_lock, flags); + return NULL; + } + desc = &ctrl->txdesc[ctrl->tx_tail]; + desc->base = ctrl->tx_base + ctrl->tx_tail * SLIM_MSGQ_BUF_LEN; + desc->comp = comp; + ctrl->tx_tail = (ctrl->tx_tail + 1) % QCOM_SLIM_NGD_DESC_NUM; + + spin_unlock_irqrestore(&ctrl->tx_buf_lock, flags); + + return desc->base; +} + +static void qcom_slim_ngd_tx_msg_dma_cb(void *args) +{ + struct qcom_slim_ngd_dma_desc *desc = args; + struct qcom_slim_ngd_ctrl *ctrl = desc->ctrl; + unsigned long flags; + + spin_lock_irqsave(&ctrl->tx_buf_lock, flags); + + if (desc->comp) { + complete(desc->comp); + desc->comp = NULL; + } + + ctrl->tx_head = (ctrl->tx_head + 1) % QCOM_SLIM_NGD_DESC_NUM; + spin_unlock_irqrestore(&ctrl->tx_buf_lock, flags); +} + +static int qcom_slim_ngd_tx_msg_post(struct qcom_slim_ngd_ctrl *ctrl, + void *buf, int len) +{ + struct qcom_slim_ngd_dma_desc *desc; + unsigned long flags; + int index, offset; + + spin_lock_irqsave(&ctrl->tx_buf_lock, flags); + offset = buf - ctrl->tx_base; + index = offset/SLIM_MSGQ_BUF_LEN; + + desc = &ctrl->txdesc[index]; + desc->phys = ctrl->tx_phys_base + offset; + desc->base = ctrl->tx_base + offset; + desc->ctrl = ctrl; + len = (len + 3) & 0xfc; + + desc->desc = dmaengine_prep_slave_single(ctrl->dma_tx_channel, + desc->phys, len, + DMA_MEM_TO_DEV, + DMA_PREP_INTERRUPT); + if (!desc->desc) { + dev_err(ctrl->dev, "unable to prepare channel\n"); + spin_unlock_irqrestore(&ctrl->tx_buf_lock, flags); + return -EINVAL; + } + + desc->desc->callback = qcom_slim_ngd_tx_msg_dma_cb; + desc->desc->callback_param = desc; + desc->desc->cookie = dmaengine_submit(desc->desc); + dma_async_issue_pending(ctrl->dma_tx_channel); + spin_unlock_irqrestore(&ctrl->tx_buf_lock, flags); + + return 0; +} + +static void qcom_slim_ngd_rx(struct qcom_slim_ngd_ctrl *ctrl, u8 *buf) +{ + u8 mc, mt, len; + + mt = SLIM_HEADER_GET_MT(buf[0]); + len = SLIM_HEADER_GET_RL(buf[0]); + mc = SLIM_HEADER_GET_MC(buf[1]); + + if (mc == SLIM_USR_MC_MASTER_CAPABILITY && + mt == SLIM_MSG_MT_SRC_REFERRED_USER) + queue_work(ctrl->mwq, &ctrl->m_work); + + if (mc == SLIM_MSG_MC_REPLY_INFORMATION || + mc == SLIM_MSG_MC_REPLY_VALUE || (mc == SLIM_USR_MC_ADDR_REPLY && + mt == SLIM_MSG_MT_SRC_REFERRED_USER)) { + slim_msg_response(&ctrl->ctrl, &buf[4], buf[3], len - 4); + pm_runtime_mark_last_busy(ctrl->dev); + } +} + +static void qcom_slim_ngd_rx_msgq_cb(void *args) +{ + struct qcom_slim_ngd_dma_desc *desc = args; + struct qcom_slim_ngd_ctrl *ctrl = desc->ctrl; + + qcom_slim_ngd_rx(ctrl, (u8 *)desc->base); + /* Add descriptor back to the queue */ + desc->desc = dmaengine_prep_slave_single(ctrl->dma_rx_channel, + desc->phys, SLIM_MSGQ_BUF_LEN, + DMA_DEV_TO_MEM, + DMA_PREP_INTERRUPT); + if (!desc->desc) { + dev_err(ctrl->dev, "Unable to prepare rx channel\n"); + return; + } + + desc->desc->callback = qcom_slim_ngd_rx_msgq_cb; + desc->desc->callback_param = desc; + desc->desc->cookie = dmaengine_submit(desc->desc); + dma_async_issue_pending(ctrl->dma_rx_channel); +} + +static int qcom_slim_ngd_post_rx_msgq(struct qcom_slim_ngd_ctrl *ctrl) +{ + struct qcom_slim_ngd_dma_desc *desc; + int i; + + for (i = 0; i < QCOM_SLIM_NGD_DESC_NUM; i++) { + desc = &ctrl->rx_desc[i]; + desc->phys = ctrl->rx_phys_base + i * SLIM_MSGQ_BUF_LEN; + desc->ctrl = ctrl; + desc->base = ctrl->rx_base + i * SLIM_MSGQ_BUF_LEN; + desc->desc = dmaengine_prep_slave_single(ctrl->dma_rx_channel, + desc->phys, SLIM_MSGQ_BUF_LEN, + DMA_DEV_TO_MEM, + DMA_PREP_INTERRUPT); + if (!desc->desc) { + dev_err(ctrl->dev, "Unable to prepare rx channel\n"); + return -EINVAL; + } + + desc->desc->callback = qcom_slim_ngd_rx_msgq_cb; + desc->desc->callback_param = desc; + desc->desc->cookie = dmaengine_submit(desc->desc); + } + dma_async_issue_pending(ctrl->dma_rx_channel); + + return 0; +} + +static int qcom_slim_ngd_init_rx_msgq(struct qcom_slim_ngd_ctrl *ctrl) +{ + struct device *dev = ctrl->dev; + int ret, size; + + ctrl->dma_rx_channel = dma_request_slave_channel(dev, "rx"); + if (!ctrl->dma_rx_channel) { + dev_err(dev, "Failed to request dma channels"); + return -EINVAL; + } + + size = QCOM_SLIM_NGD_DESC_NUM * SLIM_MSGQ_BUF_LEN; + ctrl->rx_base = dma_alloc_coherent(dev, size, &ctrl->rx_phys_base, + GFP_KERNEL); + if (!ctrl->rx_base) { + dev_err(dev, "dma_alloc_coherent failed\n"); + ret = -ENOMEM; + goto rel_rx; + } + + ret = qcom_slim_ngd_post_rx_msgq(ctrl); + if (ret) { + dev_err(dev, "post_rx_msgq() failed 0x%x\n", ret); + goto rx_post_err; + } + + return 0; + +rx_post_err: + dma_free_coherent(dev, size, ctrl->rx_base, ctrl->rx_phys_base); +rel_rx: + dma_release_channel(ctrl->dma_rx_channel); + return ret; +} + +static int qcom_slim_ngd_init_tx_msgq(struct qcom_slim_ngd_ctrl *ctrl) +{ + struct device *dev = ctrl->dev; + unsigned long flags; + int ret = 0; + int size; + + ctrl->dma_tx_channel = dma_request_slave_channel(dev, "tx"); + if (!ctrl->dma_tx_channel) { + dev_err(dev, "Failed to request dma channels"); + return -EINVAL; + } + + size = ((QCOM_SLIM_NGD_DESC_NUM + 1) * SLIM_MSGQ_BUF_LEN); + ctrl->tx_base = dma_alloc_coherent(dev, size, &ctrl->tx_phys_base, + GFP_KERNEL); + if (!ctrl->tx_base) { + dev_err(dev, "dma_alloc_coherent failed\n"); + ret = -EINVAL; + goto rel_tx; + } + + spin_lock_irqsave(&ctrl->tx_buf_lock, flags); + ctrl->tx_tail = 0; + ctrl->tx_head = 0; + spin_unlock_irqrestore(&ctrl->tx_buf_lock, flags); + + return 0; +rel_tx: + dma_release_channel(ctrl->dma_tx_channel); + return ret; +} + +static int qcom_slim_ngd_init_dma(struct qcom_slim_ngd_ctrl *ctrl) +{ + int ret = 0; + + ret = qcom_slim_ngd_init_rx_msgq(ctrl); + if (ret) { + dev_err(ctrl->dev, "rx dma init failed\n"); + return ret; + } + + ret = qcom_slim_ngd_init_tx_msgq(ctrl); + if (ret) + dev_err(ctrl->dev, "tx dma init failed\n"); + + return ret; +} + +static irqreturn_t qcom_slim_ngd_interrupt(int irq, void *d) +{ + struct qcom_slim_ngd_ctrl *ctrl = d; + void __iomem *base = ctrl->ngd->base; + u32 stat = readl(base + NGD_INT_STAT); + + if ((stat & NGD_INT_MSG_BUF_CONTE) || + (stat & NGD_INT_MSG_TX_INVAL) || (stat & NGD_INT_DEV_ERR) || + (stat & NGD_INT_TX_NACKED_2)) { + dev_err(ctrl->dev, "Error Interrupt received 0x%x\n", stat); + } + + writel(stat, base + NGD_INT_CLR); + + return IRQ_HANDLED; +} + +static int qcom_slim_ngd_xfer_msg(struct slim_controller *sctrl, + struct slim_msg_txn *txn) +{ + struct qcom_slim_ngd_ctrl *ctrl = dev_get_drvdata(sctrl->dev); + DECLARE_COMPLETION_ONSTACK(tx_sent); + int ret, timeout; + u32 *pbuf; + u8 *puc; + u8 la = txn->la; + + if (txn->mc & SLIM_MSG_CLK_PAUSE_SEQ_FLG) + return -EPROTONOSUPPORT; + + if (txn->mt == SLIM_MSG_MT_CORE && + (txn->mc >= SLIM_MSG_MC_BEGIN_RECONFIGURATION && + txn->mc <= SLIM_MSG_MC_RECONFIGURE_NOW)) + return 0; + + if (txn->dt == SLIM_MSG_DEST_ENUMADDR) + return -EPROTONOSUPPORT; + + if (txn->msg->num_bytes > SLIM_MSGQ_BUF_LEN || + txn->rl > SLIM_MSGQ_BUF_LEN) { + dev_err(ctrl->dev, "msg exeeds HW limit\n"); + return -EINVAL; + } + + pbuf = qcom_slim_ngd_tx_msg_get(ctrl, txn->rl, &tx_sent); + if (!pbuf) { + dev_err(ctrl->dev, "Message buffer unavailable\n"); + return -ENOMEM; + } + + /* HW expects length field to be excluded */ + txn->rl--; + puc = (u8 *)pbuf; + *pbuf = 0; + if (txn->dt == SLIM_MSG_DEST_LOGICALADDR) { + *pbuf = SLIM_MSG_ASM_FIRST_WORD(txn->rl, txn->mt, txn->mc, 0, + la); + puc += 3; + } else { + *pbuf = SLIM_MSG_ASM_FIRST_WORD(txn->rl, txn->mt, txn->mc, 1, + la); + puc += 2; + } + + if (slim_tid_txn(txn->mt, txn->mc)) + *(puc++) = txn->tid; + + if (slim_ec_txn(txn->mt, txn->mc)) { + *(puc++) = (txn->ec & 0xFF); + *(puc++) = (txn->ec >> 8) & 0xFF; + } + + if (txn->msg && txn->msg->wbuf) + memcpy(puc, txn->msg->wbuf, txn->msg->num_bytes); + + ret = qcom_slim_ngd_tx_msg_post(ctrl, pbuf, txn->rl); + if (ret) + return ret; + + timeout = wait_for_completion_timeout(&tx_sent, HZ); + if (!timeout) { + dev_err(sctrl->dev, "TX timed out:MC:0x%x,mt:0x%x", txn->mc, + txn->mt); + return -ETIMEDOUT; + } + + return 0; +} + +static int qcom_slim_ngd_xfer_msg_sync(struct slim_controller *ctrl, + struct slim_msg_txn *txn) +{ + DECLARE_COMPLETION_ONSTACK(done); + int ret, timeout; + + pm_runtime_get_sync(ctrl->dev); + + txn->comp = &done; + + ret = qcom_slim_ngd_xfer_msg(ctrl, txn); + if (ret) + return ret; + + timeout = wait_for_completion_timeout(&done, HZ); + if (!timeout) { + dev_err(ctrl->dev, "TX timed out:MC:0x%x,mt:0x%x", txn->mc, + txn->mt); + return -ETIMEDOUT; + } + return 0; +} + +static int qcom_slim_ngd_get_laddr(struct slim_controller *ctrl, + struct slim_eaddr *ea, u8 *laddr) +{ + struct slim_val_inf msg = {0}; + struct slim_msg_txn txn; + u8 wbuf[10] = {0}; + u8 rbuf[10] = {0}; + int ret; + + txn.mt = SLIM_MSG_MT_DEST_REFERRED_USER; + txn.dt = SLIM_MSG_DEST_LOGICALADDR; + txn.la = SLIM_LA_MGR; + txn.ec = 0; + + txn.mc = SLIM_USR_MC_ADDR_QUERY; + txn.rl = 11; + txn.msg = &msg; + txn.msg->num_bytes = 7; + txn.msg->wbuf = wbuf; + txn.msg->rbuf = rbuf; + + ret = slim_alloc_txn_tid(ctrl, &txn); + if (ret < 0) + return ret; + + wbuf[0] = (u8)txn.tid; + memcpy(&wbuf[1], ea, sizeof(*ea)); + + ret = qcom_slim_ngd_xfer_msg_sync(ctrl, &txn); + if (ret) { + slim_free_txn_tid(ctrl, &txn); + return ret; + } + + *laddr = rbuf[6]; + + return ret; +} + +static int qcom_slim_ngd_exit_dma(struct qcom_slim_ngd_ctrl *ctrl) +{ + if (ctrl->dma_rx_channel) { + dmaengine_terminate_sync(ctrl->dma_rx_channel); + dma_release_channel(ctrl->dma_rx_channel); + } + + if (ctrl->dma_tx_channel) { + dmaengine_terminate_sync(ctrl->dma_tx_channel); + dma_release_channel(ctrl->dma_tx_channel); + } + + ctrl->dma_tx_channel = ctrl->dma_rx_channel = NULL; + + return 0; +} + +static void qcom_slim_ngd_setup(struct qcom_slim_ngd_ctrl *ctrl) +{ + u32 cfg = readl_relaxed(ctrl->ngd->base); + + if (ctrl->state == QCOM_SLIM_NGD_CTRL_DOWN) + qcom_slim_ngd_init_dma(ctrl); + + /* By default enable message queues */ + cfg |= NGD_CFG_RX_MSGQ_EN; + cfg |= NGD_CFG_TX_MSGQ_EN; + + /* Enable NGD if it's not already enabled*/ + if (!(cfg & NGD_CFG_ENABLE)) + cfg |= NGD_CFG_ENABLE; + + writel_relaxed(cfg, ctrl->ngd->base); +} + +static int qcom_slim_ngd_power_up(struct qcom_slim_ngd_ctrl *ctrl) +{ + enum qcom_slim_ngd_state cur_state = ctrl->state; + struct qcom_slim_ngd *ngd = ctrl->ngd; + u32 laddr, rx_msgq; + int timeout, ret = 0; + + if (ctrl->state == QCOM_SLIM_NGD_CTRL_DOWN) { + timeout = wait_for_completion_timeout(&ctrl->qmi.qmi_comp, HZ); + if (!timeout) + return -EREMOTEIO; + } + + if (ctrl->state == QCOM_SLIM_NGD_CTRL_ASLEEP || + ctrl->state == QCOM_SLIM_NGD_CTRL_DOWN) { + ret = qcom_slim_qmi_power_request(ctrl, true); + if (ret) { + dev_err(ctrl->dev, "SLIM QMI power request failed:%d\n", + ret); + return ret; + } + } + + ctrl->ver = readl_relaxed(ctrl->base); + /* Version info in 16 MSbits */ + ctrl->ver >>= 16; + + laddr = readl_relaxed(ngd->base + NGD_STATUS); + if (laddr & NGD_LADDR) { + /* + * external MDM restart case where ADSP itself was active framer + * For example, modem restarted when playback was active + */ + if (cur_state == QCOM_SLIM_NGD_CTRL_AWAKE) { + dev_info(ctrl->dev, "Subsys restart: ADSP active framer\n"); + return 0; + } + return 0; + } + + writel_relaxed(DEF_NGD_INT_MASK, ngd->base + NGD_INT_EN); + rx_msgq = readl_relaxed(ngd->base + NGD_RX_MSGQ_CFG); + + writel_relaxed(rx_msgq|SLIM_RX_MSGQ_TIMEOUT_VAL, + ngd->base + NGD_RX_MSGQ_CFG); + qcom_slim_ngd_setup(ctrl); + + timeout = wait_for_completion_timeout(&ctrl->reconf, HZ); + if (!timeout) { + dev_err(ctrl->dev, "capability exchange timed-out\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static void qcom_slim_ngd_notify_slaves(struct qcom_slim_ngd_ctrl *ctrl) +{ + struct slim_device *sbdev; + struct device_node *node; + + for_each_child_of_node(ctrl->ngd->pdev->dev.of_node, node) { + sbdev = of_slim_get_device(&ctrl->ctrl, node); + if (!sbdev) + continue; + + if (slim_get_logical_addr(sbdev)) + dev_err(ctrl->dev, "Failed to get logical address\n"); + } +} + +static void qcom_slim_ngd_master_worker(struct work_struct *work) +{ + struct qcom_slim_ngd_ctrl *ctrl; + struct slim_msg_txn txn; + struct slim_val_inf msg = {0}; + int retries = 0; + u8 wbuf[8]; + int ret = 0; + + ctrl = container_of(work, struct qcom_slim_ngd_ctrl, m_work); + txn.dt = SLIM_MSG_DEST_LOGICALADDR; + txn.ec = 0; + txn.mc = SLIM_USR_MC_REPORT_SATELLITE; + txn.mt = SLIM_MSG_MT_SRC_REFERRED_USER; + txn.la = SLIM_LA_MGR; + wbuf[0] = SAT_MAGIC_LSB; + wbuf[1] = SAT_MAGIC_MSB; + wbuf[2] = SAT_MSG_VER; + wbuf[3] = SAT_MSG_PROT; + txn.msg = &msg; + txn.msg->wbuf = wbuf; + txn.msg->num_bytes = 4; + txn.rl = 8; + + dev_info(ctrl->dev, "SLIM SAT: Rcvd master capability\n"); + +capability_retry: + ret = qcom_slim_ngd_xfer_msg(&ctrl->ctrl, &txn); + if (!ret) { + if (ctrl->state >= QCOM_SLIM_NGD_CTRL_ASLEEP) + complete(&ctrl->reconf); + else + dev_err(ctrl->dev, "unexpected state:%d\n", + ctrl->state); + + if (ctrl->state == QCOM_SLIM_NGD_CTRL_DOWN) + qcom_slim_ngd_notify_slaves(ctrl); + + } else if (ret == -EIO) { + dev_err(ctrl->dev, "capability message NACKed, retrying\n"); + if (retries < INIT_MX_RETRIES) { + msleep(DEF_RETRY_MS); + retries++; + goto capability_retry; + } + } else { + dev_err(ctrl->dev, "SLIM: capability TX failed:%d\n", ret); + } +} + +static int qcom_slim_ngd_runtime_resume(struct device *dev) +{ + struct qcom_slim_ngd_ctrl *ctrl = dev_get_drvdata(dev); + int ret = 0; + + if (ctrl->state >= QCOM_SLIM_NGD_CTRL_ASLEEP) + ret = qcom_slim_ngd_power_up(ctrl); + if (ret) { + /* Did SSR cause this power up failure */ + if (ctrl->state != QCOM_SLIM_NGD_CTRL_DOWN) + ctrl->state = QCOM_SLIM_NGD_CTRL_ASLEEP; + else + dev_err(ctrl->dev, "HW wakeup attempt during SSR\n"); + } else { + ctrl->state = QCOM_SLIM_NGD_CTRL_AWAKE; + } + + return 0; +} + +static int qcom_slim_ngd_enable(struct qcom_slim_ngd_ctrl *ctrl, bool enable) +{ + if (enable) { + int ret = qcom_slim_qmi_init(ctrl, false); + + if (ret) { + dev_err(ctrl->dev, "qmi init fail, ret:%d, state:%d\n", + ret, ctrl->state); + return ret; + } + /* controller state should be in sync with framework state */ + complete(&ctrl->qmi.qmi_comp); + if (!pm_runtime_enabled(ctrl->dev) || + !pm_runtime_suspended(ctrl->dev)) + qcom_slim_ngd_runtime_resume(ctrl->dev); + else + pm_runtime_resume(ctrl->dev); + pm_runtime_mark_last_busy(ctrl->dev); + pm_runtime_put(ctrl->dev); + } else { + qcom_slim_qmi_exit(ctrl); + } + + return 0; +} + +static int qcom_slim_ngd_qmi_new_server(struct qmi_handle *hdl, + struct qmi_service *service) +{ + struct qcom_slim_ngd_qmi *qmi = + container_of(hdl, struct qcom_slim_ngd_qmi, svc_event_hdl); + struct qcom_slim_ngd_ctrl *ctrl = + container_of(qmi, struct qcom_slim_ngd_ctrl, qmi); + + qmi->svc_info.sq_family = AF_QIPCRTR; + qmi->svc_info.sq_node = service->node; + qmi->svc_info.sq_port = service->port; + + qcom_slim_ngd_enable(ctrl, true); + + return 0; +} + +static void qcom_slim_ngd_qmi_del_server(struct qmi_handle *hdl, + struct qmi_service *service) +{ + struct qcom_slim_ngd_qmi *qmi = + container_of(hdl, struct qcom_slim_ngd_qmi, svc_event_hdl); + + qmi->svc_info.sq_node = 0; + qmi->svc_info.sq_port = 0; +} + +static struct qmi_ops qcom_slim_ngd_qmi_svc_event_ops = { + .new_server = qcom_slim_ngd_qmi_new_server, + .del_server = qcom_slim_ngd_qmi_del_server, +}; + +static int qcom_slim_ngd_qmi_svc_event_init(struct qcom_slim_ngd_ctrl *ctrl) +{ + struct qcom_slim_ngd_qmi *qmi = &ctrl->qmi; + int ret; + + ret = qmi_handle_init(&qmi->svc_event_hdl, 0, + &qcom_slim_ngd_qmi_svc_event_ops, NULL); + if (ret < 0) { + dev_err(ctrl->dev, "qmi_handle_init failed: %d\n", ret); + return ret; + } + + ret = qmi_add_lookup(&qmi->svc_event_hdl, SLIMBUS_QMI_SVC_ID, + SLIMBUS_QMI_SVC_V1, SLIMBUS_QMI_INS_ID); + if (ret < 0) { + dev_err(ctrl->dev, "qmi_add_lookup failed: %d\n", ret); + qmi_handle_release(&qmi->svc_event_hdl); + } + return ret; +} + +static void qcom_slim_ngd_qmi_svc_event_deinit(struct qcom_slim_ngd_qmi *qmi) +{ + qmi_handle_release(&qmi->svc_event_hdl); +} + +static struct platform_driver qcom_slim_ngd_driver; +#define QCOM_SLIM_NGD_DRV_NAME "qcom,slim-ngd" + +static const struct of_device_id qcom_slim_ngd_dt_match[] = { + { + .compatible = "qcom,slim-ngd-v1.5.0", + .data = &ngd_v1_5_offset_info, + }, + {} +}; + +MODULE_DEVICE_TABLE(of, qcom_slim_ngd_dt_match); + +static int of_qcom_slim_ngd_register(struct device *parent, + struct qcom_slim_ngd_ctrl *ctrl) +{ + const struct ngd_reg_offset_data *data; + struct qcom_slim_ngd *ngd; + struct device_node *node; + u32 id; + + data = of_match_node(qcom_slim_ngd_dt_match, parent->of_node)->data; + + for_each_available_child_of_node(parent->of_node, node) { + if (of_property_read_u32(node, "reg", &id)) + continue; + + ngd = kzalloc(sizeof(*ngd), GFP_KERNEL); + if (!ngd) + return -ENOMEM; + + ngd->pdev = platform_device_alloc(QCOM_SLIM_NGD_DRV_NAME, id); + ngd->id = id; + ngd->pdev->dev.parent = parent; + ngd->pdev->driver_override = QCOM_SLIM_NGD_DRV_NAME; + ngd->pdev->dev.of_node = node; + ctrl->ngd = ngd; + platform_set_drvdata(ngd->pdev, ctrl); + + platform_device_add(ngd->pdev); + ngd->base = ctrl->base + ngd->id * data->offset + + (ngd->id - 1) * data->size; + ctrl->ngd = ngd; + platform_driver_register(&qcom_slim_ngd_driver); + + return 0; + } + + return -ENODEV; +} + +static int qcom_slim_ngd_probe(struct platform_device *pdev) +{ + struct qcom_slim_ngd_ctrl *ctrl = platform_get_drvdata(pdev); + struct device *dev = &pdev->dev; + int ret; + + ctrl->ctrl.dev = dev; + ret = slim_register_controller(&ctrl->ctrl); + if (ret) { + dev_err(dev, "error adding slim controller\n"); + return ret; + } + + pm_runtime_use_autosuspend(dev); + pm_runtime_set_autosuspend_delay(dev, QCOM_SLIM_NGD_AUTOSUSPEND); + pm_runtime_set_suspended(dev); + pm_runtime_enable(dev); + pm_runtime_get_noresume(dev); + ret = qcom_slim_ngd_qmi_svc_event_init(ctrl); + if (ret) { + dev_err(&pdev->dev, "QMI service registration failed:%d", ret); + goto err; + } + + INIT_WORK(&ctrl->m_work, qcom_slim_ngd_master_worker); + ctrl->mwq = create_singlethread_workqueue("ngd_master"); + if (!ctrl->mwq) { + dev_err(&pdev->dev, "Failed to start master worker\n"); + ret = -ENOMEM; + goto wq_err; + } + + return 0; +err: + slim_unregister_controller(&ctrl->ctrl); +wq_err: + qcom_slim_ngd_qmi_svc_event_deinit(&ctrl->qmi); + if (ctrl->mwq) + destroy_workqueue(ctrl->mwq); + + return 0; +} + +static int qcom_slim_ngd_ctrl_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct qcom_slim_ngd_ctrl *ctrl; + struct resource *res; + int ret; + + ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL); + if (!ctrl) + return -ENOMEM; + + dev_set_drvdata(dev, ctrl); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + ctrl->base = devm_ioremap_resource(dev, res); + if (IS_ERR(ctrl->base)) + return PTR_ERR(ctrl->base); + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res) { + dev_err(&pdev->dev, "no slimbus IRQ resource\n"); + return -ENODEV; + } + + ret = devm_request_irq(dev, res->start, qcom_slim_ngd_interrupt, + IRQF_TRIGGER_HIGH, "slim-ngd", ctrl); + if (ret) { + dev_err(&pdev->dev, "request IRQ failed\n"); + return ret; + } + + ctrl->dev = dev; + ctrl->framer.rootfreq = SLIM_ROOT_FREQ >> 3; + ctrl->framer.superfreq = + ctrl->framer.rootfreq / SLIM_CL_PER_SUPERFRAME_DIV8; + + ctrl->ctrl.a_framer = &ctrl->framer; + ctrl->ctrl.clkgear = SLIM_MAX_CLK_GEAR; + ctrl->ctrl.get_laddr = qcom_slim_ngd_get_laddr; + ctrl->ctrl.xfer_msg = qcom_slim_ngd_xfer_msg; + ctrl->ctrl.wakeup = NULL; + ctrl->state = QCOM_SLIM_NGD_CTRL_DOWN; + + spin_lock_init(&ctrl->tx_buf_lock); + init_completion(&ctrl->reconf); + init_completion(&ctrl->qmi.qmi_comp); + + return of_qcom_slim_ngd_register(dev, ctrl); +} + +static int qcom_slim_ngd_ctrl_remove(struct platform_device *pdev) +{ + platform_driver_unregister(&qcom_slim_ngd_driver); + + return 0; +} + +static int qcom_slim_ngd_remove(struct platform_device *pdev) +{ + struct qcom_slim_ngd_ctrl *ctrl = platform_get_drvdata(pdev); + + pm_runtime_disable(&pdev->dev); + slim_unregister_controller(&ctrl->ctrl); + qcom_slim_ngd_exit_dma(ctrl); + qcom_slim_ngd_qmi_svc_event_deinit(&ctrl->qmi); + if (ctrl->mwq) + destroy_workqueue(ctrl->mwq); + + kfree(ctrl->ngd); + ctrl->ngd = NULL; + return 0; +} + +static int qcom_slim_ngd_runtime_idle(struct device *dev) +{ + struct qcom_slim_ngd_ctrl *ctrl = dev_get_drvdata(dev); + + if (ctrl->state == QCOM_SLIM_NGD_CTRL_AWAKE) + ctrl->state = QCOM_SLIM_NGD_CTRL_IDLE; + pm_request_autosuspend(dev); + return -EAGAIN; +} + +#ifdef CONFIG_PM +static int qcom_slim_ngd_runtime_suspend(struct device *dev) +{ + struct qcom_slim_ngd_ctrl *ctrl = dev_get_drvdata(dev); + int ret = 0; + + ret = qcom_slim_qmi_power_request(ctrl, false); + if (ret && ret != -EBUSY) + dev_info(ctrl->dev, "slim resource not idle:%d\n", ret); + if (!ret || ret == -ETIMEDOUT) + ctrl->state = QCOM_SLIM_NGD_CTRL_ASLEEP; + + return ret; +} +#endif + +static const struct dev_pm_ops qcom_slim_ngd_dev_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) + SET_RUNTIME_PM_OPS( + qcom_slim_ngd_runtime_suspend, + qcom_slim_ngd_runtime_resume, + qcom_slim_ngd_runtime_idle + ) +}; + +static struct platform_driver qcom_slim_ngd_ctrl_driver = { + .probe = qcom_slim_ngd_ctrl_probe, + .remove = qcom_slim_ngd_ctrl_remove, + .driver = { + .name = "qcom,slim-ngd-ctrl", + .of_match_table = qcom_slim_ngd_dt_match, + }, +}; + +static struct platform_driver qcom_slim_ngd_driver = { + .probe = qcom_slim_ngd_probe, + .remove = qcom_slim_ngd_remove, + .driver = { + .name = QCOM_SLIM_NGD_DRV_NAME, + .pm = &qcom_slim_ngd_dev_pm_ops, + }, +}; + +module_platform_driver(qcom_slim_ngd_ctrl_driver); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Qualcomm SLIMBus NGD controller"); diff --git a/drivers/slimbus/slimbus.h b/drivers/slimbus/slimbus.h index 2ba6545fa716..63229e8cd050 100644 --- a/drivers/slimbus/slimbus.h +++ b/drivers/slimbus/slimbus.h @@ -17,6 +17,8 @@ /* SLIMbus message types. Related to interpretation of message code. */ #define SLIM_MSG_MT_CORE 0x0 +#define SLIM_MSG_MT_DEST_REFERRED_USER 0x2 +#define SLIM_MSG_MT_SRC_REFERRED_USER 0x6 /* * SLIM Broadcast header format @@ -48,6 +50,12 @@ #define SLIM_MSG_MC_NEXT_PAUSE_CLOCK 0x4A #define SLIM_MSG_MC_RECONFIGURE_NOW 0x5F +/* + * Clock pause flag to indicate that the reconfig message + * corresponds to clock pause sequence + */ +#define SLIM_MSG_CLK_PAUSE_SEQ_FLG (1U << 8) + /* Clock pause values per SLIMbus spec */ #define SLIM_CLK_FAST 0 #define SLIM_CLK_CONST_PHASE 1 -- cgit v1.2.3 From abb9c9b8b51ba53b47ad7685ad2a0a64dbbf7bf5 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Thu, 5 Jul 2018 14:54:25 +0100 Subject: slimbus: stream: add stream support This patch adds support to SLIMbus stream apis for slimbus device. SLIMbus streaming involves adding support to Data Channel Management and channel Reconfiguration Messages to slim core plus few stream apis. >From slim device side the apis are very simple mostly inline with other stream apis. Currently it only supports Isochronous and Push/Pull transport protocols, which are sufficient for audio use cases. Signed-off-by: Srinivas Kandagatla Signed-off-by: Greg Kroah-Hartman --- Documentation/driver-api/slimbus.rst | 5 + drivers/slimbus/Makefile | 2 +- drivers/slimbus/core.c | 2 + drivers/slimbus/slimbus.h | 189 ++++++++++++++ drivers/slimbus/stream.c | 477 +++++++++++++++++++++++++++++++++++ include/linux/slimbus.h | 46 ++++ 6 files changed, 720 insertions(+), 1 deletion(-) create mode 100644 drivers/slimbus/stream.c diff --git a/Documentation/driver-api/slimbus.rst b/Documentation/driver-api/slimbus.rst index a97449cf603a..410eec79b2a1 100644 --- a/Documentation/driver-api/slimbus.rst +++ b/Documentation/driver-api/slimbus.rst @@ -125,3 +125,8 @@ Messaging APIs: ~~~~~~~~~~~~~~~ .. kernel-doc:: drivers/slimbus/messaging.c :export: + +Streaming APIs: +~~~~~~~~~~~~~~~ +.. kernel-doc:: drivers/slimbus/stream.c + :export: diff --git a/drivers/slimbus/Makefile b/drivers/slimbus/Makefile index c78c6e16c2df..d9aa011b6804 100644 --- a/drivers/slimbus/Makefile +++ b/drivers/slimbus/Makefile @@ -3,7 +3,7 @@ # Makefile for kernel SLIMbus framework. # obj-$(CONFIG_SLIMBUS) += slimbus.o -slimbus-y := core.o messaging.o sched.o +slimbus-y := core.o messaging.o sched.o stream.o #Controllers obj-$(CONFIG_SLIM_QCOM_CTRL) += slim-qcom-ctrl.o diff --git a/drivers/slimbus/core.c b/drivers/slimbus/core.c index 88248a4ecad9..95b00d28ad6e 100644 --- a/drivers/slimbus/core.c +++ b/drivers/slimbus/core.c @@ -114,6 +114,8 @@ static int slim_add_device(struct slim_controller *ctrl, sbdev->dev.release = slim_dev_release; sbdev->dev.driver = NULL; sbdev->ctrl = ctrl; + INIT_LIST_HEAD(&sbdev->stream_list); + spin_lock_init(&sbdev->stream_list_lock); if (node) sbdev->dev.of_node = of_node_get(node); diff --git a/drivers/slimbus/slimbus.h b/drivers/slimbus/slimbus.h index 63229e8cd050..6dbc2b320704 100644 --- a/drivers/slimbus/slimbus.h +++ b/drivers/slimbus/slimbus.h @@ -45,9 +45,20 @@ #define SLIM_MSG_MC_ASSIGN_LOGICAL_ADDRESS 0x2 #define SLIM_MSG_MC_REPORT_ABSENT 0xF +/* Data channel management messages */ +#define SLIM_MSG_MC_CONNECT_SOURCE 0x10 +#define SLIM_MSG_MC_CONNECT_SINK 0x11 +#define SLIM_MSG_MC_DISCONNECT_PORT 0x14 +#define SLIM_MSG_MC_CHANGE_CONTENT 0x18 + /* Clock pause Reconfiguration messages */ #define SLIM_MSG_MC_BEGIN_RECONFIGURATION 0x40 #define SLIM_MSG_MC_NEXT_PAUSE_CLOCK 0x4A +#define SLIM_MSG_MC_NEXT_DEFINE_CHANNEL 0x50 +#define SLIM_MSG_MC_NEXT_DEFINE_CONTENT 0x51 +#define SLIM_MSG_MC_NEXT_ACTIVATE_CHANNEL 0x54 +#define SLIM_MSG_MC_NEXT_DEACTIVATE_CHANNEL 0x55 +#define SLIM_MSG_MC_NEXT_REMOVE_CHANNEL 0x58 #define SLIM_MSG_MC_RECONFIGURE_NOW 0x5F /* @@ -69,7 +80,15 @@ /* Standard values per SLIMbus spec needed by controllers and devices */ #define SLIM_MAX_CLK_GEAR 10 #define SLIM_MIN_CLK_GEAR 1 +#define SLIM_SLOT_LEN_BITS 4 + +/* Indicate that the frequency of the flow and the bus frequency are locked */ +#define SLIM_CHANNEL_CONTENT_FL BIT(7) +/* Standard values per SLIMbus spec needed by controllers and devices */ +#define SLIM_CL_PER_SUPERFRAME 6144 +#define SLIM_SLOTS_PER_SUPERFRAME (SLIM_CL_PER_SUPERFRAME >> 2) +#define SLIM_SL_PER_SUPERFRAME (SLIM_CL_PER_SUPERFRAME >> 2) /* Manager's logical address is set to 0xFF per spec */ #define SLIM_LA_MANAGER 0xFF @@ -167,6 +186,170 @@ struct slim_sched { struct mutex m_reconf; }; +/** + * enum slim_port_direction: SLIMbus port direction + * + * @SLIM_PORT_SINK: SLIMbus port is a sink + * @SLIM_PORT_SOURCE: SLIMbus port is a source + */ +enum slim_port_direction { + SLIM_PORT_SINK = 0, + SLIM_PORT_SOURCE, +}; +/** + * enum slim_port_state: SLIMbus Port/Endpoint state machine + * according to SLIMbus Spec 2.0 + * @SLIM_PORT_DISCONNECTED: SLIMbus port is disconnected + * entered from Unconfigure/configured state after + * DISCONNECT_PORT or REMOVE_CHANNEL core command + * @SLIM_PORT_UNCONFIGURED: SLIMbus port is in unconfigured state. + * entered from disconnect state after CONNECT_SOURCE/SINK core command + * @SLIM_PORT_CONFIGURED: SLIMbus port is in configured state. + * entered from unconfigured state after DEFINE_CHANNEL, DEFINE_CONTENT + * and ACTIVATE_CHANNEL core commands. Ready for data transmission. + */ +enum slim_port_state { + SLIM_PORT_DISCONNECTED = 0, + SLIM_PORT_UNCONFIGURED, + SLIM_PORT_CONFIGURED, +}; + +/** + * enum slim_channel_state: SLIMbus channel state machine used by core. + * @SLIM_CH_STATE_DISCONNECTED: SLIMbus channel is disconnected + * @SLIM_CH_STATE_ALLOCATED: SLIMbus channel is allocated + * @SLIM_CH_STATE_ASSOCIATED: SLIMbus channel is associated with port + * @SLIM_CH_STATE_DEFINED: SLIMbus channel parameters are defined + * @SLIM_CH_STATE_CONTENT_DEFINED: SLIMbus channel content is defined + * @SLIM_CH_STATE_ACTIVE: SLIMbus channel is active and ready for data + * @SLIM_CH_STATE_REMOVED: SLIMbus channel is inactive and removed + */ +enum slim_channel_state { + SLIM_CH_STATE_DISCONNECTED = 0, + SLIM_CH_STATE_ALLOCATED, + SLIM_CH_STATE_ASSOCIATED, + SLIM_CH_STATE_DEFINED, + SLIM_CH_STATE_CONTENT_DEFINED, + SLIM_CH_STATE_ACTIVE, + SLIM_CH_STATE_REMOVED, +}; + +/** + * enum slim_ch_data_fmt: SLIMbus channel data Type identifiers according to + * Table 60 of SLIMbus Spec 1.01.01 + * @SLIM_CH_DATA_FMT_NOT_DEFINED: Undefined + * @SLIM_CH_DATA_FMT_LPCM_AUDIO: LPCM audio + * @SLIM_CH_DATA_FMT_IEC61937_COMP_AUDIO: IEC61937 Compressed audio + * @SLIM_CH_DATA_FMT_PACKED_PDM_AUDIO: Packed PDM audio + */ +enum slim_ch_data_fmt { + SLIM_CH_DATA_FMT_NOT_DEFINED = 0, + SLIM_CH_DATA_FMT_LPCM_AUDIO = 1, + SLIM_CH_DATA_FMT_IEC61937_COMP_AUDIO = 2, + SLIM_CH_DATA_FMT_PACKED_PDM_AUDIO = 3, +}; + +/** + * enum slim_ch_aux_fmt: SLIMbus channel Aux Field format IDs according to + * Table 63 of SLIMbus Spec 2.0 + * @SLIM_CH_AUX_FMT_NOT_APPLICABLE: Undefined + * @SLIM_CH_AUX_FMT_ZCUV_TUNNEL_IEC60958: ZCUV for tunneling IEC60958 + * @SLIM_CH_AUX_FMT_USER_DEFINED: User defined + */ +enum slim_ch_aux_bit_fmt { + SLIM_CH_AUX_FMT_NOT_APPLICABLE = 0, + SLIM_CH_AUX_FMT_ZCUV_TUNNEL_IEC60958 = 1, + SLIM_CH_AUX_FMT_USER_DEFINED = 0xF, +}; + +/** + * struct slim_channel - SLIMbus channel, used for state machine + * + * @id: ID of channel + * @prrate: Presense rate of channel from Table 66 of SLIMbus 2.0 Specs + * @seg_dist: segment distribution code from Table 20 of SLIMbus 2.0 Specs + * @data_fmt: Data format of channel. + * @aux_fmt: Aux format for this channel. + * @state: channel state machine + */ +struct slim_channel { + int id; + int prrate; + int seg_dist; + enum slim_ch_data_fmt data_fmt; + enum slim_ch_aux_bit_fmt aux_fmt; + enum slim_channel_state state; +}; + +/** + * struct slim_port - SLIMbus port + * + * @id: Port id + * @direction: Port direction, Source or Sink. + * @state: state machine of port. + * @ch: channel associated with this port. + */ +struct slim_port { + int id; + enum slim_port_direction direction; + enum slim_port_state state; + struct slim_channel ch; +}; + +/** + * enum slim_transport_protocol: SLIMbus Transport protocol list from + * Table 47 of SLIMbus 2.0 specs. + * @SLIM_PROTO_ISO: Isochronous Protocol, no flow control as data rate match + * channel rate flow control embedded in the data. + * @SLIM_PROTO_PUSH: Pushed Protocol, includes flow control, Used to carry + * data whose rate is equal to, or lower than the channel rate. + * @SLIM_PROTO_PULL: Pulled Protocol, similar usage as pushed protocol + * but pull is a unicast. + * @SLIM_PROTO_LOCKED: Locked Protocol + * @SLIM_PROTO_ASYNC_SMPLX: Asynchronous Protocol-Simplex + * @SLIM_PROTO_ASYNC_HALF_DUP: Asynchronous Protocol-Half-duplex + * @SLIM_PROTO_EXT_SMPLX: Extended Asynchronous Protocol-Simplex + * @SLIM_PROTO_EXT_HALF_DUP: Extended Asynchronous Protocol-Half-duplex + */ +enum slim_transport_protocol { + SLIM_PROTO_ISO = 0, + SLIM_PROTO_PUSH, + SLIM_PROTO_PULL, + SLIM_PROTO_LOCKED, + SLIM_PROTO_ASYNC_SMPLX, + SLIM_PROTO_ASYNC_HALF_DUP, + SLIM_PROTO_EXT_SMPLX, + SLIM_PROTO_EXT_HALF_DUP, +}; + +/** + * struct slim_stream_runtime - SLIMbus stream runtime instance + * + * @dev: Name of the stream + * @dev: SLIM Device instance associated with this stream + * @state: state of stream + * @direction: direction of stream + * @prot: Transport protocol used in this stream + * @rate: Data rate of samples * + * @bps: bits per sample + * @ratem: rate multipler which is super frame rate/data rate + * @num_ports: number of ports + * @ports: pointer to instance of ports + * @node: list head for stream associated with slim device. + */ +struct slim_stream_runtime { + const char *name; + struct slim_device *dev; + int direction; + enum slim_transport_protocol prot; + unsigned int rate; + unsigned int bps; + unsigned int ratem; + int num_ports; + struct slim_port *ports; + struct list_head node; +}; + /** * struct slim_controller - Controls every instance of SLIMbus * (similar to 'master' on SPI) @@ -196,6 +379,10 @@ struct slim_sched { * @wakeup: This function pointer implements controller-specific procedure * to wake it up from clock-pause. Framework will call this to bring * the controller out of clock pause. + * @enable_stream: This function pointer implements controller-specific procedure + * to enable a stream. + * @disable_stream: This function pointer implements controller-specific procedure + * to disable stream. * * 'Manager device' is responsible for device management, bandwidth * allocation, channel setup, and port associations per channel. @@ -237,6 +424,8 @@ struct slim_controller { struct slim_eaddr *ea, u8 laddr); int (*get_laddr)(struct slim_controller *ctrl, struct slim_eaddr *ea, u8 *laddr); + int (*enable_stream)(struct slim_stream_runtime *rt); + int (*disable_stream)(struct slim_stream_runtime *rt); int (*wakeup)(struct slim_controller *ctrl); }; diff --git a/drivers/slimbus/stream.c b/drivers/slimbus/stream.c new file mode 100644 index 000000000000..2fa05324ed07 --- /dev/null +++ b/drivers/slimbus/stream.c @@ -0,0 +1,477 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2018, Linaro Limited + +#include +#include +#include +#include +#include +#include +#include "slimbus.h" + +/** + * struct segdist_code - Segment Distributions code from + * Table 20 of SLIMbus Specs Version 2.0 + * + * @ratem: Channel Rate Multipler(Segments per Superframe) + * @seg_interval: Number of slots between the first Slot of Segment + * and the first slot of the next consecutive Segment. + * @segdist_code: Segment Distribution Code SD[11:0] + * @seg_offset_mask: Segment offset mask in SD[11:0] + * @segdist_codes: List of all possible Segmet Distribution codes. + */ +static const struct segdist_code { + int ratem; + int seg_interval; + int segdist_code; + u32 seg_offset_mask; + +} segdist_codes[] = { + {1, 1536, 0x200, 0xdff}, + {2, 768, 0x100, 0xcff}, + {4, 384, 0x080, 0xc7f}, + {8, 192, 0x040, 0xc3f}, + {16, 96, 0x020, 0xc1f}, + {32, 48, 0x010, 0xc0f}, + {64, 24, 0x008, 0xc07}, + {128, 12, 0x004, 0xc03}, + {256, 6, 0x002, 0xc01}, + {512, 3, 0x001, 0xc00}, + {3, 512, 0xe00, 0x1ff}, + {6, 256, 0xd00, 0x0ff}, + {12, 128, 0xc80, 0x07f}, + {24, 64, 0xc40, 0x03f}, + {48, 32, 0xc20, 0x01f}, + {96, 16, 0xc10, 0x00f}, + {192, 8, 0xc08, 0x007}, + {364, 4, 0xc04, 0x003}, + {768, 2, 0xc02, 0x001}, +}; + +/* + * Presence Rate table for all Natural Frequencies + * The Presence rate of a constant bitrate stream is mean flow rate of the + * stream expressed in occupied Segments of that Data Channel per second. + * Table 66 from SLIMbus 2.0 Specs + * + * Index of the table corresponds to Presence rate code for the respective rate + * in the table. + */ +static const int slim_presence_rate_table[] = { + 0, /* Not Indicated */ + 12000, + 24000, + 48000, + 96000, + 192000, + 384000, + 768000, + 0, /* Reserved */ + 110250, + 220500, + 441000, + 882000, + 176400, + 352800, + 705600, + 4000, + 8000, + 16000, + 32000, + 64000, + 128000, + 256000, + 512000, +}; + +/* + * slim_stream_allocate() - Allocate a new SLIMbus Stream + * @dev:Slim device to be associated with + * @name: name of the stream + * + * This is very first call for SLIMbus streaming, this API will allocate + * a new SLIMbus stream and return a valid stream runtime pointer for client + * to use it in subsequent stream apis. state of stream is set to ALLOCATED + * + * Return: valid pointer on success and error code on failure. + * From ASoC DPCM framework, this state is linked to startup() operation. + */ +struct slim_stream_runtime *slim_stream_allocate(struct slim_device *dev, + const char *name) +{ + struct slim_stream_runtime *rt; + + rt = kzalloc(sizeof(*rt), GFP_KERNEL); + if (!rt) + return ERR_PTR(-ENOMEM); + + rt->name = kasprintf(GFP_KERNEL, "slim-%s", name); + if (!rt->name) { + kfree(rt); + return ERR_PTR(-ENOMEM); + } + + rt->dev = dev; + spin_lock(&dev->stream_list_lock); + list_add_tail(&rt->node, &dev->stream_list); + spin_unlock(&dev->stream_list_lock); + + return rt; +} +EXPORT_SYMBOL_GPL(slim_stream_allocate); + +static int slim_connect_port_channel(struct slim_stream_runtime *stream, + struct slim_port *port) +{ + struct slim_device *sdev = stream->dev; + u8 wbuf[2]; + struct slim_val_inf msg = {0, 2, NULL, wbuf, NULL}; + u8 mc = SLIM_MSG_MC_CONNECT_SOURCE; + DEFINE_SLIM_LDEST_TXN(txn, mc, 6, stream->dev->laddr, &msg); + + if (port->direction == SLIM_PORT_SINK) + txn.mc = SLIM_MSG_MC_CONNECT_SINK; + + wbuf[0] = port->id; + wbuf[1] = port->ch.id; + port->ch.state = SLIM_CH_STATE_ASSOCIATED; + port->state = SLIM_PORT_UNCONFIGURED; + + return slim_do_transfer(sdev->ctrl, &txn); +} + +static int slim_disconnect_port(struct slim_stream_runtime *stream, + struct slim_port *port) +{ + struct slim_device *sdev = stream->dev; + u8 wbuf[1]; + struct slim_val_inf msg = {0, 1, NULL, wbuf, NULL}; + u8 mc = SLIM_MSG_MC_DISCONNECT_PORT; + DEFINE_SLIM_LDEST_TXN(txn, mc, 5, stream->dev->laddr, &msg); + + wbuf[0] = port->id; + port->ch.state = SLIM_CH_STATE_DISCONNECTED; + port->state = SLIM_PORT_DISCONNECTED; + + return slim_do_transfer(sdev->ctrl, &txn); +} + +static int slim_deactivate_remove_channel(struct slim_stream_runtime *stream, + struct slim_port *port) +{ + struct slim_device *sdev = stream->dev; + u8 wbuf[1]; + struct slim_val_inf msg = {0, 1, NULL, wbuf, NULL}; + u8 mc = SLIM_MSG_MC_NEXT_DEACTIVATE_CHANNEL; + DEFINE_SLIM_LDEST_TXN(txn, mc, 5, stream->dev->laddr, &msg); + int ret; + + wbuf[0] = port->ch.id; + ret = slim_do_transfer(sdev->ctrl, &txn); + if (ret) + return ret; + + txn.mc = SLIM_MSG_MC_NEXT_REMOVE_CHANNEL; + port->ch.state = SLIM_CH_STATE_REMOVED; + + return slim_do_transfer(sdev->ctrl, &txn); +} + +static int slim_get_prate_code(int rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(slim_presence_rate_table); i++) { + if (rate == slim_presence_rate_table[i]) + return i; + } + + return -EINVAL; +} + +/* + * slim_stream_prepare() - Prepare a SLIMbus Stream + * + * @rt: instance of slim stream runtime to configure + * @cfg: new configuration for the stream + * + * This API will configure SLIMbus stream with config parameters from cfg. + * return zero on success and error code on failure. From ASoC DPCM framework, + * this state is linked to hw_params() operation. + */ +int slim_stream_prepare(struct slim_stream_runtime *rt, + struct slim_stream_config *cfg) +{ + struct slim_controller *ctrl = rt->dev->ctrl; + struct slim_port *port; + int num_ports, i, port_id; + + if (rt->ports) { + dev_err(&rt->dev->dev, "Stream already Prepared\n"); + return -EINVAL; + } + + num_ports = hweight32(cfg->port_mask); + rt->ports = kcalloc(num_ports, sizeof(*port), GFP_KERNEL); + if (!rt->ports) + return -ENOMEM; + + rt->num_ports = num_ports; + rt->rate = cfg->rate; + rt->bps = cfg->bps; + rt->direction = cfg->direction; + + if (cfg->rate % ctrl->a_framer->superfreq) { + /* + * data rate not exactly multiple of super frame, + * use PUSH/PULL protocol + */ + if (cfg->direction == SNDRV_PCM_STREAM_PLAYBACK) + rt->prot = SLIM_PROTO_PUSH; + else + rt->prot = SLIM_PROTO_PULL; + } else { + rt->prot = SLIM_PROTO_ISO; + } + + rt->ratem = cfg->rate/ctrl->a_framer->superfreq; + + i = 0; + for_each_set_bit(port_id, &cfg->port_mask, SLIM_DEVICE_MAX_PORTS) { + port = &rt->ports[i]; + port->state = SLIM_PORT_DISCONNECTED; + port->id = port_id; + port->ch.prrate = slim_get_prate_code(cfg->rate); + port->ch.id = cfg->chs[i]; + port->ch.data_fmt = SLIM_CH_DATA_FMT_NOT_DEFINED; + port->ch.aux_fmt = SLIM_CH_AUX_FMT_NOT_APPLICABLE; + port->ch.state = SLIM_CH_STATE_ALLOCATED; + + if (cfg->direction == SNDRV_PCM_STREAM_PLAYBACK) + port->direction = SLIM_PORT_SINK; + else + port->direction = SLIM_PORT_SOURCE; + + slim_connect_port_channel(rt, port); + i++; + } + + return 0; +} +EXPORT_SYMBOL_GPL(slim_stream_prepare); + +static int slim_define_channel_content(struct slim_stream_runtime *stream, + struct slim_port *port) +{ + struct slim_device *sdev = stream->dev; + u8 wbuf[4]; + struct slim_val_inf msg = {0, 4, NULL, wbuf, NULL}; + u8 mc = SLIM_MSG_MC_NEXT_DEFINE_CONTENT; + DEFINE_SLIM_LDEST_TXN(txn, mc, 8, stream->dev->laddr, &msg); + + wbuf[0] = port->ch.id; + wbuf[1] = port->ch.prrate; + + /* Frequency Locked for ISO Protocol */ + if (stream->prot != SLIM_PROTO_ISO) + wbuf[1] |= SLIM_CHANNEL_CONTENT_FL; + + wbuf[2] = port->ch.data_fmt | (port->ch.aux_fmt << 4); + wbuf[3] = stream->bps/SLIM_SLOT_LEN_BITS; + port->ch.state = SLIM_CH_STATE_CONTENT_DEFINED; + + return slim_do_transfer(sdev->ctrl, &txn); +} + +static int slim_get_segdist_code(int ratem) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(segdist_codes); i++) { + if (segdist_codes[i].ratem == ratem) + return segdist_codes[i].segdist_code; + } + + return -EINVAL; +} + +static int slim_define_channel(struct slim_stream_runtime *stream, + struct slim_port *port) +{ + struct slim_device *sdev = stream->dev; + u8 wbuf[4]; + struct slim_val_inf msg = {0, 4, NULL, wbuf, NULL}; + u8 mc = SLIM_MSG_MC_NEXT_DEFINE_CHANNEL; + DEFINE_SLIM_LDEST_TXN(txn, mc, 8, stream->dev->laddr, &msg); + + port->ch.seg_dist = slim_get_segdist_code(stream->ratem); + + wbuf[0] = port->ch.id; + wbuf[1] = port->ch.seg_dist & 0xFF; + wbuf[2] = (stream->prot << 4) | ((port->ch.seg_dist & 0xF00) >> 8); + if (stream->prot == SLIM_PROTO_ISO) + wbuf[3] = stream->bps/SLIM_SLOT_LEN_BITS; + else + wbuf[3] = stream->bps/SLIM_SLOT_LEN_BITS + 1; + + port->ch.state = SLIM_CH_STATE_DEFINED; + + return slim_do_transfer(sdev->ctrl, &txn); +} + +static int slim_activate_channel(struct slim_stream_runtime *stream, + struct slim_port *port) +{ + struct slim_device *sdev = stream->dev; + u8 wbuf[1]; + struct slim_val_inf msg = {0, 1, NULL, wbuf, NULL}; + u8 mc = SLIM_MSG_MC_NEXT_ACTIVATE_CHANNEL; + DEFINE_SLIM_LDEST_TXN(txn, mc, 5, stream->dev->laddr, &msg); + + txn.msg->num_bytes = 1; + txn.msg->wbuf = wbuf; + wbuf[0] = port->ch.id; + port->ch.state = SLIM_CH_STATE_ACTIVE; + + return slim_do_transfer(sdev->ctrl, &txn); +} + +/* + * slim_stream_enable() - Enable a prepared SLIMbus Stream + * + * @stream: instance of slim stream runtime to enable + * + * This API will enable all the ports and channels associated with + * SLIMbus stream + * + * Return: zero on success and error code on failure. From ASoC DPCM framework, + * this state is linked to trigger() start operation. + */ +int slim_stream_enable(struct slim_stream_runtime *stream) +{ + DEFINE_SLIM_BCAST_TXN(txn, SLIM_MSG_MC_BEGIN_RECONFIGURATION, + 3, SLIM_LA_MANAGER, NULL); + struct slim_controller *ctrl = stream->dev->ctrl; + int ret, i; + + if (ctrl->enable_stream) { + ret = ctrl->enable_stream(stream); + if (ret) + return ret; + + for (i = 0; i < stream->num_ports; i++) + stream->ports[i].ch.state = SLIM_CH_STATE_ACTIVE; + + return ret; + } + + ret = slim_do_transfer(ctrl, &txn); + if (ret) + return ret; + + /* define channels first before activating them */ + for (i = 0; i < stream->num_ports; i++) { + struct slim_port *port = &stream->ports[i]; + + slim_define_channel(stream, port); + slim_define_channel_content(stream, port); + } + + for (i = 0; i < stream->num_ports; i++) { + struct slim_port *port = &stream->ports[i]; + + slim_activate_channel(stream, port); + port->state = SLIM_PORT_CONFIGURED; + } + txn.mc = SLIM_MSG_MC_RECONFIGURE_NOW; + + return slim_do_transfer(ctrl, &txn); +} +EXPORT_SYMBOL_GPL(slim_stream_enable); + +/* + * slim_stream_disable() - Disable a SLIMbus Stream + * + * @stream: instance of slim stream runtime to disable + * + * This API will disable all the ports and channels associated with + * SLIMbus stream + * + * Return: zero on success and error code on failure. From ASoC DPCM framework, + * this state is linked to trigger() pause operation. + */ +int slim_stream_disable(struct slim_stream_runtime *stream) +{ + DEFINE_SLIM_BCAST_TXN(txn, SLIM_MSG_MC_BEGIN_RECONFIGURATION, + 3, SLIM_LA_MANAGER, NULL); + struct slim_controller *ctrl = stream->dev->ctrl; + int ret, i; + + if (ctrl->disable_stream) + ctrl->disable_stream(stream); + + ret = slim_do_transfer(ctrl, &txn); + if (ret) + return ret; + + for (i = 0; i < stream->num_ports; i++) + slim_deactivate_remove_channel(stream, &stream->ports[i]); + + txn.mc = SLIM_MSG_MC_RECONFIGURE_NOW; + + return slim_do_transfer(ctrl, &txn); +} +EXPORT_SYMBOL_GPL(slim_stream_disable); + +/* + * slim_stream_unprepare() - Un-prepare a SLIMbus Stream + * + * @stream: instance of slim stream runtime to unprepare + * + * This API will un allocate all the ports and channels associated with + * SLIMbus stream + * + * Return: zero on success and error code on failure. From ASoC DPCM framework, + * this state is linked to trigger() stop operation. + */ +int slim_stream_unprepare(struct slim_stream_runtime *stream) +{ + int i; + + for (i = 0; i < stream->num_ports; i++) + slim_disconnect_port(stream, &stream->ports[i]); + + kfree(stream->ports); + stream->ports = NULL; + stream->num_ports = 0; + + return 0; +} +EXPORT_SYMBOL_GPL(slim_stream_unprepare); + +/* + * slim_stream_free() - Free a SLIMbus Stream + * + * @stream: instance of slim stream runtime to free + * + * This API will un allocate all the memory associated with + * slim stream runtime, user is not allowed to make an dereference + * to stream after this call. + * + * Return: zero on success and error code on failure. From ASoC DPCM framework, + * this state is linked to shutdown() operation. + */ +int slim_stream_free(struct slim_stream_runtime *stream) +{ + struct slim_device *sdev = stream->dev; + + spin_lock(&sdev->stream_list_lock); + list_del(&stream->node); + spin_unlock(&sdev->stream_list_lock); + + kfree(stream->name); + kfree(stream); + + return 0; +} +EXPORT_SYMBOL_GPL(slim_stream_free); diff --git a/include/linux/slimbus.h b/include/linux/slimbus.h index 63801bcc5e60..12c9719b2a55 100644 --- a/include/linux/slimbus.h +++ b/include/linux/slimbus.h @@ -48,6 +48,8 @@ struct slim_controller; * @ctrl: slim controller instance. * @laddr: 1-byte Logical address of this device. * @is_laddr_valid: indicates if the laddr is valid or not + * @stream_list: List of streams on this device + * @stream_list_lock: lock to protect the stream list * * This is the client/device handle returned when a SLIMbus * device is registered with a controller. @@ -60,6 +62,8 @@ struct slim_device { enum slim_device_status status; u8 laddr; bool is_laddr_valid; + struct list_head stream_list; + spinlock_t stream_list_lock; }; #define to_slim_device(d) container_of(d, struct slim_device, dev) @@ -108,6 +112,36 @@ struct slim_val_inf { struct completion *comp; }; +#define SLIM_DEVICE_MAX_CHANNELS 256 +/* A SLIMBus Device may have frmo 0 to 31 Ports (inclusive) */ +#define SLIM_DEVICE_MAX_PORTS 32 + +/** + * struct slim_stream_config - SLIMbus stream configuration + * Configuring a stream is done at hw_params or prepare call + * from audio drivers where they have all the required information + * regarding rate, number of channels and so on. + * There is a 1:1 mapping of channel and ports. + * + * @rate: data rate + * @bps: bits per data sample + * @ch_count: number of channels + * @chs: pointer to list of channel numbers + * @port_mask: port mask of ports to use for this stream + * @direction: direction of the stream, SNDRV_PCM_STREAM_PLAYBACK + * or SNDRV_PCM_STREAM_CAPTURE. + */ +struct slim_stream_config { + unsigned int rate; + unsigned int bps; + /* MAX 256 channels */ + unsigned int ch_count; + unsigned int *chs; + /* Max 32 ports per device */ + unsigned long port_mask; + int direction; +}; + /* * use a macro to avoid include chaining to get THIS_MODULE */ @@ -163,4 +197,16 @@ int slim_readb(struct slim_device *sdev, u32 addr); int slim_writeb(struct slim_device *sdev, u32 addr, u8 value); int slim_read(struct slim_device *sdev, u32 addr, size_t count, u8 *val); int slim_write(struct slim_device *sdev, u32 addr, size_t count, u8 *val); + +/* SLIMbus Stream apis */ +struct slim_stream_runtime; +struct slim_stream_runtime *slim_stream_allocate(struct slim_device *dev, + const char *sname); +int slim_stream_prepare(struct slim_stream_runtime *stream, + struct slim_stream_config *c); +int slim_stream_enable(struct slim_stream_runtime *stream); +int slim_stream_disable(struct slim_stream_runtime *stream); +int slim_stream_unprepare(struct slim_stream_runtime *stream); +int slim_stream_free(struct slim_stream_runtime *stream); + #endif /* _LINUX_SLIMBUS_H */ -- cgit v1.2.3 From 52490169cddf55a1174772c6bf7be32db2ce01e0 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Thu, 5 Jul 2018 14:54:26 +0100 Subject: slimbus: ngd: add stream support This patch adds support to stream support, this involve implementing user specific implementation of Data channel management and channel management SLIMbus messages. Signed-off-by: Srinivas Kandagatla Signed-off-by: Greg Kroah-Hartman --- drivers/slimbus/qcom-ngd-ctrl.c | 149 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 147 insertions(+), 2 deletions(-) diff --git a/drivers/slimbus/qcom-ngd-ctrl.c b/drivers/slimbus/qcom-ngd-ctrl.c index 8554e3f43522..8be4d6786c61 100644 --- a/drivers/slimbus/qcom-ngd-ctrl.c +++ b/drivers/slimbus/qcom-ngd-ctrl.c @@ -603,7 +603,9 @@ static void qcom_slim_ngd_rx(struct qcom_slim_ngd_ctrl *ctrl, u8 *buf) if (mc == SLIM_MSG_MC_REPLY_INFORMATION || mc == SLIM_MSG_MC_REPLY_VALUE || (mc == SLIM_USR_MC_ADDR_REPLY && - mt == SLIM_MSG_MT_SRC_REFERRED_USER)) { + mt == SLIM_MSG_MT_SRC_REFERRED_USER) || + (mc == SLIM_USR_MC_GENERIC_ACK && + mt == SLIM_MSG_MT_SRC_REFERRED_USER)) { slim_msg_response(&ctrl->ctrl, &buf[4], buf[3], len - 4); pm_runtime_mark_last_busy(ctrl->dev); } @@ -766,10 +768,14 @@ static int qcom_slim_ngd_xfer_msg(struct slim_controller *sctrl, { struct qcom_slim_ngd_ctrl *ctrl = dev_get_drvdata(sctrl->dev); DECLARE_COMPLETION_ONSTACK(tx_sent); - int ret, timeout; + DECLARE_COMPLETION_ONSTACK(done); + int ret, timeout, i; + u8 wbuf[SLIM_MSGQ_BUF_LEN]; + u8 rbuf[SLIM_MSGQ_BUF_LEN]; u32 *pbuf; u8 *puc; u8 la = txn->la; + bool usr_msg = false; if (txn->mc & SLIM_MSG_CLK_PAUSE_SEQ_FLG) return -EPROTONOSUPPORT; @@ -794,6 +800,48 @@ static int qcom_slim_ngd_xfer_msg(struct slim_controller *sctrl, return -ENOMEM; } + if (txn->mt == SLIM_MSG_MT_CORE && + (txn->mc == SLIM_MSG_MC_CONNECT_SOURCE || + txn->mc == SLIM_MSG_MC_CONNECT_SINK || + txn->mc == SLIM_MSG_MC_DISCONNECT_PORT)) { + txn->mt = SLIM_MSG_MT_DEST_REFERRED_USER; + switch (txn->mc) { + case SLIM_MSG_MC_CONNECT_SOURCE: + txn->mc = SLIM_USR_MC_CONNECT_SRC; + break; + case SLIM_MSG_MC_CONNECT_SINK: + txn->mc = SLIM_USR_MC_CONNECT_SINK; + break; + case SLIM_MSG_MC_DISCONNECT_PORT: + txn->mc = SLIM_USR_MC_DISCONNECT_PORT; + break; + default: + return -EINVAL; + } + + usr_msg = true; + i = 0; + wbuf[i++] = txn->la; + la = SLIM_LA_MGR; + wbuf[i++] = txn->msg->wbuf[0]; + if (txn->mc != SLIM_USR_MC_DISCONNECT_PORT) + wbuf[i++] = txn->msg->wbuf[1]; + + txn->comp = &done; + ret = slim_alloc_txn_tid(sctrl, txn); + if (ret) { + dev_err(ctrl->dev, "Unable to allocate TID\n"); + return ret; + } + + wbuf[i++] = txn->tid; + + txn->msg->num_bytes = i; + txn->msg->wbuf = wbuf; + txn->msg->rbuf = rbuf; + txn->rl = txn->msg->num_bytes + 4; + } + /* HW expects length field to be excluded */ txn->rl--; puc = (u8 *)pbuf; @@ -830,6 +878,15 @@ static int qcom_slim_ngd_xfer_msg(struct slim_controller *sctrl, return -ETIMEDOUT; } + if (usr_msg) { + timeout = wait_for_completion_timeout(&done, HZ); + if (!timeout) { + dev_err(sctrl->dev, "TX timed out:MC:0x%x,mt:0x%x", + txn->mc, txn->mt); + return -ETIMEDOUT; + } + } + return 0; } @@ -856,6 +913,93 @@ static int qcom_slim_ngd_xfer_msg_sync(struct slim_controller *ctrl, return 0; } +static int qcom_slim_ngd_enable_stream(struct slim_stream_runtime *rt) +{ + struct slim_device *sdev = rt->dev; + struct slim_controller *ctrl = sdev->ctrl; + struct slim_val_inf msg = {0}; + u8 wbuf[SLIM_MSGQ_BUF_LEN]; + u8 rbuf[SLIM_MSGQ_BUF_LEN]; + struct slim_msg_txn txn = {0,}; + int i, ret; + + txn.mt = SLIM_MSG_MT_DEST_REFERRED_USER; + txn.dt = SLIM_MSG_DEST_LOGICALADDR; + txn.la = SLIM_LA_MGR; + txn.ec = 0; + txn.msg = &msg; + txn.msg->num_bytes = 0; + txn.msg->wbuf = wbuf; + txn.msg->rbuf = rbuf; + + for (i = 0; i < rt->num_ports; i++) { + struct slim_port *port = &rt->ports[i]; + + if (txn.msg->num_bytes == 0) { + int seg_interval = SLIM_SLOTS_PER_SUPERFRAME/rt->ratem; + int exp; + + wbuf[txn.msg->num_bytes++] = sdev->laddr; + wbuf[txn.msg->num_bytes] = rt->bps >> 2 | + (port->ch.aux_fmt << 6); + + /* Data channel segment interval not multiple of 3 */ + exp = seg_interval % 3; + if (exp) + wbuf[txn.msg->num_bytes] |= BIT(5); + + txn.msg->num_bytes++; + wbuf[txn.msg->num_bytes++] = exp << 4 | rt->prot; + + if (rt->prot == SLIM_PROTO_ISO) + wbuf[txn.msg->num_bytes++] = + port->ch.prrate | + SLIM_CHANNEL_CONTENT_FL; + else + wbuf[txn.msg->num_bytes++] = port->ch.prrate; + + ret = slim_alloc_txn_tid(ctrl, &txn); + if (ret) { + dev_err(&sdev->dev, "Fail to allocate TID\n"); + return -ENXIO; + } + wbuf[txn.msg->num_bytes++] = txn.tid; + } + wbuf[txn.msg->num_bytes++] = port->ch.id; + } + + txn.mc = SLIM_USR_MC_DEF_ACT_CHAN; + txn.rl = txn.msg->num_bytes + 4; + ret = qcom_slim_ngd_xfer_msg_sync(ctrl, &txn); + if (ret) { + slim_free_txn_tid(ctrl, &txn); + dev_err(&sdev->dev, "TX timed out:MC:0x%x,mt:0x%x", txn.mc, + txn.mt); + return ret; + } + + txn.mc = SLIM_USR_MC_RECONFIG_NOW; + txn.msg->num_bytes = 2; + wbuf[1] = sdev->laddr; + txn.rl = txn.msg->num_bytes + 4; + + ret = slim_alloc_txn_tid(ctrl, &txn); + if (ret) { + dev_err(ctrl->dev, "Fail to allocate TID\n"); + return ret; + } + + wbuf[0] = txn.tid; + ret = qcom_slim_ngd_xfer_msg_sync(ctrl, &txn); + if (ret) { + slim_free_txn_tid(ctrl, &txn); + dev_err(&sdev->dev, "TX timed out:MC:0x%x,mt:0x%x", txn.mc, + txn.mt); + } + + return ret; +} + static int qcom_slim_ngd_get_laddr(struct slim_controller *ctrl, struct slim_eaddr *ea, u8 *laddr) { @@ -1288,6 +1432,7 @@ static int qcom_slim_ngd_ctrl_probe(struct platform_device *pdev) ctrl->ctrl.a_framer = &ctrl->framer; ctrl->ctrl.clkgear = SLIM_MAX_CLK_GEAR; ctrl->ctrl.get_laddr = qcom_slim_ngd_get_laddr; + ctrl->ctrl.enable_stream = qcom_slim_ngd_enable_stream; ctrl->ctrl.xfer_msg = qcom_slim_ngd_xfer_msg; ctrl->ctrl.wakeup = NULL; ctrl->state = QCOM_SLIM_NGD_CTRL_DOWN; -- cgit v1.2.3 From 98860b7b605ce6b5017b5870dba4492155128280 Mon Sep 17 00:00:00 2001 From: Henriette Hofmeier Date: Thu, 14 Jun 2018 17:20:39 +0200 Subject: ds2490.c: Add required spaces Add missing spaces in for- and while-loops reported missing by checkpatch. Signed-off-by: Henriette Hofmeier Signed-off-by: Florian Harbecke Signed-off-by: Greg Kroah-Hartman --- drivers/w1/masters/ds2490.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/w1/masters/ds2490.c b/drivers/w1/masters/ds2490.c index c423bdb982bb..917a5f13ab58 100644 --- a/drivers/w1/masters/ds2490.c +++ b/drivers/w1/masters/ds2490.c @@ -236,7 +236,7 @@ static void ds_dump_status(struct ds_device *dev, unsigned char *buf, int count) int i; pr_info("0x%x: count=%d, status: ", dev->ep[EP_STATUS], count); - for (i=0; i= 0) { int i; printk("0x%x: count=%d, status: ", dev->ep[EP_STATUS], err); - for (i=0; ist_buf[i]); printk("\n"); } -- cgit v1.2.3 From b2dd10ce8b620f6aaafe0874c7eec4b2e275a497 Mon Sep 17 00:00:00 2001 From: Henriette Hofmeier Date: Thu, 14 Jun 2018 17:20:40 +0200 Subject: ds2490.c: Remove unnecessary whitespace Remove unnecessary whitespace criticized by checkpatch. Signed-off-by: Henriette Hofmeier Signed-off-by: Florian Harbecke Signed-off-by: Greg Kroah-Hartman --- drivers/w1/masters/ds2490.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/w1/masters/ds2490.c b/drivers/w1/masters/ds2490.c index 917a5f13ab58..1e4b243072f0 100644 --- a/drivers/w1/masters/ds2490.c +++ b/drivers/w1/masters/ds2490.c @@ -613,7 +613,7 @@ static int ds_read_byte(struct ds_device *dev, u8 *byte) int err; struct ds_status st; - err = ds_send_control(dev, COMM_BYTE_IO | COMM_IM , 0xff); + err = ds_send_control(dev, COMM_BYTE_IO | COMM_IM, 0xff); if (err) return err; -- cgit v1.2.3 From 7b4bcbcdb2a9c16ec8d83f43c04c4dd4db0df032 Mon Sep 17 00:00:00 2001 From: Henriette Hofmeier Date: Thu, 14 Jun 2018 17:20:41 +0200 Subject: ds2490.c: Move struct open braces to previous line Move open braces of two structs to the declaration line, as criticized by checkpatch. Signed-off-by: Henriette Hofmeier Signed-off-by: Florian Harbecke Signed-off-by: Greg Kroah-Hartman --- drivers/w1/masters/ds2490.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/w1/masters/ds2490.c b/drivers/w1/masters/ds2490.c index 1e4b243072f0..0f4ecfcdb549 100644 --- a/drivers/w1/masters/ds2490.c +++ b/drivers/w1/masters/ds2490.c @@ -134,8 +134,7 @@ #define EP_DATA_OUT 2 #define EP_DATA_IN 3 -struct ds_device -{ +struct ds_device { struct list_head ds_entry; struct usb_device *udev; @@ -158,8 +157,7 @@ struct ds_device struct w1_bus_master master; }; -struct ds_status -{ +struct ds_status { u8 enable; u8 speed; u8 pullup_dur; -- cgit v1.2.3 From c7e5a5b4f0371dac0888e2be7c59273d8e4c013f Mon Sep 17 00:00:00 2001 From: Mariusz Bialonczyk Date: Thu, 5 Jul 2018 06:51:36 +0200 Subject: w1: fix w1_ds2438 documentation The previous documentation was wrongly stating about the order of magnitude of CONVERT_V result files contents (vad, vdd). This commit is correcting this. Reported-by: Adam Stolarczyk Signed-off-by: Mariusz Bialonczyk Signed-off-by: Greg Kroah-Hartman --- Documentation/w1/slaves/w1_ds2438 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/w1/slaves/w1_ds2438 b/Documentation/w1/slaves/w1_ds2438 index b99f3674c5b4..e64f65a09387 100644 --- a/Documentation/w1/slaves/w1_ds2438 +++ b/Documentation/w1/slaves/w1_ds2438 @@ -60,4 +60,4 @@ vad: general purpose A/D input (VAD) vdd: battery input (VDD) After the voltage conversion the value is returned as decimal ASCII. -Note: The value is in mV, so to get a volts the value has to be divided by 10. +Note: To get a volts the value has to be divided by 100. -- cgit v1.2.3 From 55d7d44eb21dd7b88d7f17747bdc96e415bde75c Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Fri, 22 Jun 2018 12:08:20 +0200 Subject: MAINTAINERS: Add file patterns for w1 device tree bindings Submitters of device tree binding documentation may forget to CC the subsystem maintainer if this is missing. Signed-off-by: Geert Uytterhoeven Signed-off-by: Greg Kroah-Hartman --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS b/MAINTAINERS index 4b1169b8af88..b51c8cb6faed 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15394,6 +15394,7 @@ F: drivers/mmc/host/vub300.c W1 DALLAS'S 1-WIRE BUS M: Evgeniy Polyakov S: Maintained +F: Documentation/devicetree/bindings/w1/ F: Documentation/w1/ F: drivers/w1/ F: include/linux/w1.h -- cgit v1.2.3 From 26d79b820b2773eabdc512c84d1152f0f631b2fc Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Wed, 20 Jun 2018 11:26:00 -0700 Subject: nvmem: rave-sp-eeprom: Remove VLA usage In the quest to remove all stack VLA usage from the kernel[1], this uses the maximum allocation size for the stack and adds a sanity check, similar to what has already be done for the regular rave-sp driver. [1] https://lkml.kernel.org/r/CA+55aFzCG-zNmZwX4A2FQpadafLfEzK6CC=qPXydAacU1RqZWA@mail.gmail.com Signed-off-by: Kees Cook Reviewed-by: Andrey Smirnov Tested-by: Andrey Smirnov Signed-off-by: Greg Kroah-Hartman --- drivers/nvmem/rave-sp-eeprom.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/nvmem/rave-sp-eeprom.c b/drivers/nvmem/rave-sp-eeprom.c index 50aeea6ec6cc..66699d44f73d 100644 --- a/drivers/nvmem/rave-sp-eeprom.c +++ b/drivers/nvmem/rave-sp-eeprom.c @@ -35,6 +35,7 @@ enum rave_sp_eeprom_header_size { RAVE_SP_EEPROM_HEADER_SMALL = 4U, RAVE_SP_EEPROM_HEADER_BIG = 5U, }; +#define RAVE_SP_EEPROM_HEADER_MAX RAVE_SP_EEPROM_HEADER_BIG #define RAVE_SP_EEPROM_PAGE_SIZE 32U @@ -97,9 +98,12 @@ static int rave_sp_eeprom_io(struct rave_sp_eeprom *eeprom, const unsigned int rsp_size = is_write ? sizeof(*page) - sizeof(page->data) : sizeof(*page); unsigned int offset = 0; - u8 cmd[cmd_size]; + u8 cmd[RAVE_SP_EEPROM_HEADER_MAX + sizeof(page->data)]; int ret; + if (WARN_ON(cmd_size > sizeof(cmd))) + return -EINVAL; + cmd[offset++] = eeprom->address; cmd[offset++] = 0; cmd[offset++] = type; -- cgit v1.2.3 From fc82975aea38da8d605ff2fae3f871ac2929b67d Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Tue, 26 Jun 2018 12:36:40 +0100 Subject: nvmem: core: remove unused nvmem_device ncells member nvmem ncells can be over written by calling nvmem_add_cells() multiple times. I see there is no real point of maintaining count of cells when we have a list of cell. Remove this to avoid any confusion! Signed-off-by: Srinivas Kandagatla Signed-off-by: Greg Kroah-Hartman --- drivers/nvmem/core.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c index b5b0cdc21d01..34b71b1c69f4 100644 --- a/drivers/nvmem/core.c +++ b/drivers/nvmem/core.c @@ -31,7 +31,6 @@ struct nvmem_device { struct device dev; int stride; int word_size; - int ncells; int id; int users; size_t size; @@ -389,7 +388,6 @@ int nvmem_add_cells(struct nvmem_device *nvmem, nvmem_cell_add(cells[i]); } - nvmem->ncells = ncells; /* remove tmp array */ kfree(cells); -- cgit v1.2.3 From 7fb2fd4e25fc1fb10dcb30b5519de257cfeae84c Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Wed, 4 Jul 2018 12:33:34 +0300 Subject: vmci: type promotion bug in qp_host_get_user_memory() The problem is that if get_user_pages_fast() fails and returns a negative error code, it gets type promoted to a high positive value and treated as a success. Fixes: 06164d2b72aa ("VMCI: queue pairs implementation.") Signed-off-by: Dan Carpenter Signed-off-by: Greg Kroah-Hartman --- drivers/misc/vmw_vmci/vmci_queue_pair.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/misc/vmw_vmci/vmci_queue_pair.c b/drivers/misc/vmw_vmci/vmci_queue_pair.c index 9ee47244a308..bd52f29b4a4e 100644 --- a/drivers/misc/vmw_vmci/vmci_queue_pair.c +++ b/drivers/misc/vmw_vmci/vmci_queue_pair.c @@ -668,7 +668,7 @@ static int qp_host_get_user_memory(u64 produce_uva, retval = get_user_pages_fast((uintptr_t) 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) { + if (retval < (int)produce_q->kernel_if->num_pages) { pr_debug("get_user_pages_fast(produce) failed (retval=%d)", retval); qp_release_pages(produce_q->kernel_if->u.h.header_page, @@ -680,7 +680,7 @@ static int qp_host_get_user_memory(u64 produce_uva, retval = get_user_pages_fast((uintptr_t) consume_uva, consume_q->kernel_if->num_pages, 1, consume_q->kernel_if->u.h.header_page); - if (retval < consume_q->kernel_if->num_pages) { + if (retval < (int)consume_q->kernel_if->num_pages) { pr_debug("get_user_pages_fast(consume) failed (retval=%d)", retval); qp_release_pages(consume_q->kernel_if->u.h.header_page, -- cgit v1.2.3 From 5faecb0162883df2b06965c7e6dd7c42278f7e0d Mon Sep 17 00:00:00 2001 From: "Gustavo A. R. Silva" Date: Mon, 2 Jul 2018 12:57:24 -0500 Subject: /dev/mem: Mark expected switch fall-through In preparation to enabling -Wimplicit-fallthrough, mark switch cases where we are expecting to fall through. Signed-off-by: Gustavo A. R. Silva Signed-off-by: Greg Kroah-Hartman --- drivers/char/mem.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/char/mem.c b/drivers/char/mem.c index ffeb60d3434c..4b00d6adcccf 100644 --- a/drivers/char/mem.c +++ b/drivers/char/mem.c @@ -765,6 +765,7 @@ static loff_t memory_lseek(struct file *file, loff_t offset, int orig) switch (orig) { case SEEK_CUR: offset += file->f_pos; + /* fall through */ case SEEK_SET: /* to avoid userland mistaking f_pos=-9 as -EBADF=-9 */ if ((unsigned long long)offset >= -MAX_ERRNO) { -- cgit v1.2.3 From 97b715b62e5b4c6edb75d023f556fd09a46cb4e1 Mon Sep 17 00:00:00 2001 From: Wei Yongjun Date: Sat, 26 May 2018 09:45:59 +0000 Subject: misc: ibmvmc: Use GFP_ATOMIC under spin lock The function alloc_dma_buffer() is called from ibmvmc_add_buffer(), in which a spin lock be held here, so we should use GFP_ATOMIC when a lock is held. Fixes: 0eca353e7ae7 ("misc: IBM Virtual Management Channel Driver (VMC)") Signed-off-by: Wei Yongjun Reviewed-by: Bryant G. Ly Signed-off-by: Greg Kroah-Hartman --- drivers/misc/ibmvmc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/misc/ibmvmc.c b/drivers/misc/ibmvmc.c index fb83d1375638..8f82bb9d11e2 100644 --- a/drivers/misc/ibmvmc.c +++ b/drivers/misc/ibmvmc.c @@ -273,7 +273,7 @@ static void *alloc_dma_buffer(struct vio_dev *vdev, size_t size, dma_addr_t *dma_handle) { /* allocate memory */ - void *buffer = kzalloc(size, GFP_KERNEL); + void *buffer = kzalloc(size, GFP_ATOMIC); if (!buffer) { *dma_handle = 0; -- cgit v1.2.3 From 0c2ccd8cd1436d79010354029f43e4d167f1d755 Mon Sep 17 00:00:00 2001 From: "Gustavo A. R. Silva" Date: Tue, 3 Jul 2018 17:47:36 -0500 Subject: eeprom: at25: mark expected switch fall-throughs In preparation to enabling -Wimplicit-fallthrough, mark switch cases where we are expecting to fall through. Signed-off-by: Gustavo A. R. Silva Signed-off-by: Greg Kroah-Hartman --- drivers/misc/eeprom/at25.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/misc/eeprom/at25.c b/drivers/misc/eeprom/at25.c index 6a7d4a2ad514..840afb398f9e 100644 --- a/drivers/misc/eeprom/at25.c +++ b/drivers/misc/eeprom/at25.c @@ -94,8 +94,10 @@ static int at25_ee_read(void *priv, unsigned int offset, switch (at25->addrlen) { default: /* case 3 */ *cp++ = offset >> 16; + /* fall through */ case 2: *cp++ = offset >> 8; + /* fall through */ case 1: case 0: /* can't happen: for better codegen */ *cp++ = offset >> 0; @@ -180,8 +182,10 @@ static int at25_ee_write(void *priv, unsigned int off, void *val, size_t count) switch (at25->addrlen) { default: /* case 3 */ *cp++ = offset >> 16; + /* fall through */ case 2: *cp++ = offset >> 8; + /* fall through */ case 1: case 0: /* can't happen: for better codegen */ *cp++ = offset >> 0; -- cgit v1.2.3 From f1a54cddbbc36ebf1e8ce10a3e1fa5f8a4ce9613 Mon Sep 17 00:00:00 2001 From: "Gustavo A. R. Silva" Date: Tue, 3 Jul 2018 17:43:43 -0500 Subject: sgi-xp: xpc_partition: mark expected switch fall-throughs In preparation to enabling -Wimplicit-fallthrough, mark switch cases where we are expecting to fall through. Signed-off-by: Gustavo A. R. Silva Signed-off-by: Greg Kroah-Hartman --- drivers/misc/sgi-xp/xpc_partition.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/misc/sgi-xp/xpc_partition.c b/drivers/misc/sgi-xp/xpc_partition.c index 4e59b01ad7eb..0c3ef6f1df54 100644 --- a/drivers/misc/sgi-xp/xpc_partition.c +++ b/drivers/misc/sgi-xp/xpc_partition.c @@ -446,8 +446,10 @@ xpc_discovery(void) switch (region_size) { case 128: max_regions *= 2; + /* fall through */ case 64: max_regions *= 2; + /* fall through */ case 32: max_regions *= 2; region_size = 16; -- cgit v1.2.3 From bcde98fcf9cf084f6ed6c1751d2ef68877a87c20 Mon Sep 17 00:00:00 2001 From: "Gustavo A. R. Silva" Date: Tue, 3 Jul 2018 17:36:29 -0500 Subject: misc: mic: mark expected switch fall-through In preparation to enabling -Wimplicit-fallthrough, mark switch cases where we are expecting to fall through. Signed-off-by: Gustavo A. R. Silva Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mic/scif/scif_api.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/misc/mic/scif/scif_api.c b/drivers/misc/mic/scif/scif_api.c index 7b2dddcdd46d..463f06d0b4ef 100644 --- a/drivers/misc/mic/scif/scif_api.c +++ b/drivers/misc/mic/scif/scif_api.c @@ -187,6 +187,7 @@ int scif_close(scif_epd_t epd) case SCIFEP_ZOMBIE: dev_err(scif_info.mdev.this_device, "SCIFAPI close: zombie state unexpected\n"); + /* fall through */ case SCIFEP_DISCONNECTED: spin_unlock(&ep->lock); scif_unregister_all_windows(epd); -- cgit v1.2.3 From 7e6f7d24535b7c1edec3605d85c7684f7388d122 Mon Sep 17 00:00:00 2001 From: Uwe Kleine-König Date: Tue, 19 Jun 2018 11:38:54 +0200 Subject: siox: treat type errors as status errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The type bits are part of the per-device status word. So it's natural to consider an error in the type bits as a status error instead of only resulting in an unsynced state. Signed-off-by: Uwe Kleine-König Acked-by: Gavin Schenk Signed-off-by: Greg Kroah-Hartman --- drivers/siox/siox-core.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/drivers/siox/siox-core.c b/drivers/siox/siox-core.c index 16590dfaafa4..3115f930fa83 100644 --- a/drivers/siox/siox-core.c +++ b/drivers/siox/siox-core.c @@ -215,26 +215,26 @@ static void siox_poll(struct siox_master *smaster) siox_status_clean(status, sdevice->status_written_lastcycle); - /* Check counter bits */ - if (siox_device_counter_error(sdevice, status_clean)) { - bool prev_counter_error; + /* Check counter and type bits */ + if (siox_device_counter_error(sdevice, status_clean) || + siox_device_type_error(sdevice, status_clean)) { + bool prev_error; synced = false; /* only report a new error if the last cycle was ok */ - prev_counter_error = + prev_error = siox_device_counter_error(sdevice, - prev_status_clean); - if (!prev_counter_error) { + prev_status_clean) || + siox_device_type_error(sdevice, + prev_status_clean); + + if (!prev_error) { sdevice->status_errors++; sysfs_notify_dirent(sdevice->status_errors_kn); } } - /* Check type bits */ - if (siox_device_type_error(sdevice, status_clean)) - synced = false; - /* If the device is unsynced report the watchdog as active */ if (!synced) { status &= ~SIOX_STATUS_WDG; -- cgit v1.2.3 From e890591413819eeb604207ad3261ba617b2ec0bb Mon Sep 17 00:00:00 2001 From: Uwe Kleine-König Date: Thu, 28 Jun 2018 09:57:42 +0200 Subject: siox: don't create a thread without starting it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a siox master device is registered a kthread is created that is only started when triggered by userspace. So this thread might be in TASK_UNINTERRUPTIBLE state for long and trigger a warning [ 241.130465] INFO: task siox-0:626 blocked for more than 120 seconds. with the respective debug settings enabled. It might be right to put an unstarted thread to TASK_IDLE (in kernel/kthread.c:kthread()) instead, but independant of this discussion it is cleaner for siox_master_register() to start the thread immediately. The effect is that it enters its own waiting state and then stays in state TASK_IDLE which doesn't trigger the above warning. As siox_poll_thread() uses some variables of the device the initialisation of these is moved before thread creation. Acked-by: Peter Zijlstra (Intel) Signed-off-by: Uwe Kleine-König Acked-by: Gavin Schenk Signed-off-by: Greg Kroah-Hartman --- drivers/siox/siox-core.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/siox/siox-core.c b/drivers/siox/siox-core.c index 3115f930fa83..f8c08fb9891d 100644 --- a/drivers/siox/siox-core.c +++ b/drivers/siox/siox-core.c @@ -715,17 +715,17 @@ int siox_master_register(struct siox_master *smaster) dev_set_name(&smaster->dev, "siox-%d", smaster->busno); + mutex_init(&smaster->lock); + INIT_LIST_HEAD(&smaster->devices); + smaster->last_poll = jiffies; - smaster->poll_thread = kthread_create(siox_poll_thread, smaster, - "siox-%d", smaster->busno); + smaster->poll_thread = kthread_run(siox_poll_thread, smaster, + "siox-%d", smaster->busno); if (IS_ERR(smaster->poll_thread)) { smaster->active = 0; return PTR_ERR(smaster->poll_thread); } - mutex_init(&smaster->lock); - INIT_LIST_HEAD(&smaster->devices); - ret = device_add(&smaster->dev); if (ret) kthread_stop(smaster->poll_thread); -- cgit v1.2.3 From 6051e79bbfa6111c3a56a86fa8c6ff9a248e30a6 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Mon, 18 Jun 2018 16:25:51 +0200 Subject: misc: mic: fix passing the current time I noticed that the mic driver passes a 'struct timespec64' as part of a message into an attached device, where it is used to set the current system time. This won't actually work if one of the two sides runs a 32-bit kernel and the other runs a 64-bit kernel, since the structure layout is different between the two. I found this while replacing calls to the deprecated do_settimeofday64() interface with the modern ktime_get_real_ts() variant, but it seems appropriate to address both at the same time here. To make sure we have a sane structure, let's define our own structure using the layout of the 64-bit kernel. Signed-off-by: Arnd Bergmann Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mic/cosm/cosm_main.h | 5 ++++- drivers/misc/mic/cosm/cosm_scif_server.c | 6 +++++- drivers/misc/mic/cosm_client/cosm_scif_client.c | 6 +++++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/drivers/misc/mic/cosm/cosm_main.h b/drivers/misc/mic/cosm/cosm_main.h index f01156fca881..aa78cdf25e40 100644 --- a/drivers/misc/mic/cosm/cosm_main.h +++ b/drivers/misc/mic/cosm/cosm_main.h @@ -45,7 +45,10 @@ struct cosm_msg { u64 id; union { u64 shutdown_status; - struct timespec64 timespec; + struct { + u64 tv_sec; + u64 tv_nsec; + } timespec; }; }; diff --git a/drivers/misc/mic/cosm/cosm_scif_server.c b/drivers/misc/mic/cosm/cosm_scif_server.c index 05a63286741c..e94b7eac4a06 100644 --- a/drivers/misc/mic/cosm/cosm_scif_server.c +++ b/drivers/misc/mic/cosm/cosm_scif_server.c @@ -179,9 +179,13 @@ static void cosm_set_crashed(struct cosm_device *cdev) static void cosm_send_time(struct cosm_device *cdev) { struct cosm_msg msg = { .id = COSM_MSG_SYNC_TIME }; + struct timespec64 ts; int rc; - getnstimeofday64(&msg.timespec); + ktime_get_real_ts64(&ts); + msg.timespec.tv_sec = ts.tv_sec; + msg.timespec.tv_nsec = ts.tv_nsec; + rc = scif_send(cdev->epd, &msg, sizeof(msg), SCIF_SEND_BLOCK); if (rc < 0) dev_err(&cdev->dev, "%s %d scif_send failed rc %d\n", diff --git a/drivers/misc/mic/cosm_client/cosm_scif_client.c b/drivers/misc/mic/cosm_client/cosm_scif_client.c index beafc0da4027..225078cb51fd 100644 --- a/drivers/misc/mic/cosm_client/cosm_scif_client.c +++ b/drivers/misc/mic/cosm_client/cosm_scif_client.c @@ -63,7 +63,11 @@ static struct notifier_block cosm_reboot = { /* Set system time from timespec value received from the host */ static void cosm_set_time(struct cosm_msg *msg) { - int rc = do_settimeofday64(&msg->timespec); + struct timespec64 ts = { + .tv_sec = msg->timespec.tv_sec, + .tv_nsec = msg->timespec.tv_nsec, + }; + int rc = do_settimeofday64(&ts); if (rc) dev_err(&client_spdev->dev, "%s: %d settimeofday rc %d\n", -- cgit v1.2.3 From ce054546cc2c26891cefa2f284d90d93b52205de Mon Sep 17 00:00:00 2001 From: Matt Ranostay Date: Fri, 8 Jun 2018 23:58:15 -0700 Subject: tsl2550: fix lux1_input error in low light ADC channel 0 photodiode detects both infrared + visible light, but ADC channel 1 just detects infrared. However, the latter is a bit more sensitive in that range so complete darkness or low light causes a error condition in which the chan0 - chan1 is negative that results in a -EAGAIN. This patch changes the resulting lux1_input sysfs attribute message from "Resource temporarily unavailable" to a user-grokable lux value of 0. Cc: Arnd Bergmann Cc: Greg Kroah-Hartman Signed-off-by: Matt Ranostay Signed-off-by: Greg Kroah-Hartman --- drivers/misc/tsl2550.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/misc/tsl2550.c b/drivers/misc/tsl2550.c index adf46072cb37..3fce3b6a3624 100644 --- a/drivers/misc/tsl2550.c +++ b/drivers/misc/tsl2550.c @@ -177,7 +177,7 @@ static int tsl2550_calculate_lux(u8 ch0, u8 ch1) } else lux = 0; else - return -EAGAIN; + return 0; /* LUX range check */ return lux > TSL2550_MAX_LUX ? TSL2550_MAX_LUX : lux; -- cgit v1.2.3 From 34fe3cfda826e221d15610aa07364384bf8adb94 Mon Sep 17 00:00:00 2001 From: Alexander Kapshuk Date: Thu, 31 May 2018 22:22:46 +0300 Subject: ver_linux: Process input coming from procmaps that matches libc only Currently, input coming from /proc/self/maps is split into fields without checking whether or not it matches libc.so. This is not efficient. All text processing should only be performed on lines of input that match libc.so. Signed-off-by: Alexander Kapshuk Signed-off-by: Greg Kroah-Hartman --- scripts/ver_linux | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/scripts/ver_linux b/scripts/ver_linux index 7227994ccf63..e1dc041f903f 100755 --- a/scripts/ver_linux +++ b/scripts/ver_linux @@ -32,11 +32,13 @@ BEGIN { printversion("Nfs-utils", version("showmount --version")) while (getline <"/proc/self/maps" > 0) { - n = split($0, procmaps, "/") - if (/libc.*so$/ && match(procmaps[n], /[0-9]+([.]?[0-9]+)+/)) { - ver = substr(procmaps[n], RSTART, RLENGTH) - printversion("Linux C Library", ver) - break + if (/libc.*\.so$/) { + n = split($0, procmaps, "/") + if (match(procmaps[n], /[0-9]+([.]?[0-9]+)+/)) { + ver = substr(procmaps[n], RSTART, RLENGTH) + printversion("Linux C Library", ver) + break + } } } -- cgit v1.2.3 From 71bdd87f58f6791e50ddd7a078c47f9803c523ee Mon Sep 17 00:00:00 2001 From: Alexander Kapshuk Date: Thu, 31 May 2018 22:22:47 +0300 Subject: ver_linux: Do not check for ver_linux pattern in version function Checking whether output of commands matches the ver_linux pattern in the version function is original shell implementation legacy code. When the original implementation failed to locate a particular utility, it generated error output along the lines of: ver_linux:line number: command not found. The awk implementation, does not contain the name of the script within the body of the error message returned by the subshell when a given utility fails to be located. The error message returned is along the lines of: sh: name of utility: command not found Safeguarding against the ver_linux pattern being found in the output being parsed may thus be safely omitted. Signed-off-by: Alexander Kapshuk Signed-off-by: Greg Kroah-Hartman --- scripts/ver_linux | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/ver_linux b/scripts/ver_linux index e1dc041f903f..a6c728db05ce 100755 --- a/scripts/ver_linux +++ b/scripts/ver_linux @@ -70,7 +70,7 @@ BEGIN { function version(cmd, ver) { cmd = cmd " 2>&1" while (cmd | getline > 0) { - if (!/ver_linux/ && match($0, /[0-9]+([.]?[0-9]+)+/)) { + if (match($0, /[0-9]+([.]?[0-9]+)+/)) { ver = substr($0, RSTART, RLENGTH) break } -- cgit v1.2.3 From 2e0e3a24ca50519219ebdd471e8b65043a10196b Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Wed, 6 Jun 2018 14:40:56 +0200 Subject: android: binder: Drop dependency on !M68K As of commit 7124330dabe5b3cb ("m68k/uaccess: Revive 64-bit get_user()"), the 64-bit Android binder interface builds fine on m68k. Signed-off-by: Geert Uytterhoeven Signed-off-by: Greg Kroah-Hartman --- drivers/android/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/android/Kconfig b/drivers/android/Kconfig index ee4880bfdcdc..432e9ad77070 100644 --- a/drivers/android/Kconfig +++ b/drivers/android/Kconfig @@ -10,7 +10,7 @@ if ANDROID config ANDROID_BINDER_IPC bool "Android Binder IPC Driver" - depends on MMU && !M68K + depends on MMU default n ---help--- Binder is used in Android for both communication between processes, -- cgit v1.2.3 From 591217d19b00bfdc48c19b65c667523d3f61ff33 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Thu, 21 Jun 2018 21:00:16 +0200 Subject: misc: ti-st: Allow compile test of GPIO consumers if !GPIOLIB The GPIO subsystem provides dummy GPIO consumer functions if GPIOLIB is not enabled. Hence drivers that depend on GPIOLIB, but use GPIO consumer functionality only, can still be compiled if GPIOLIB is not enabled. Relax the dependency on GPIOLIB if COMPILE_TEST is enabled, where appropriate. Signed-off-by: Geert Uytterhoeven Acked-by: Linus Walleij Signed-off-by: Greg Kroah-Hartman --- drivers/misc/ti-st/Kconfig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/misc/ti-st/Kconfig b/drivers/misc/ti-st/Kconfig index f34dcc514730..5bb92698bc80 100644 --- a/drivers/misc/ti-st/Kconfig +++ b/drivers/misc/ti-st/Kconfig @@ -5,7 +5,8 @@ menu "Texas Instruments shared transport line discipline" config TI_ST tristate "Shared transport core driver" - depends on NET && GPIOLIB && TTY + depends on NET && TTY + depends on GPIOLIB || COMPILE_TEST select FW_LOADER help This enables the shared transport core driver for TI -- cgit v1.2.3 From ac3167257b9fe16c9426c2087ead1c9f1b0992b1 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Tue, 19 Jun 2018 22:47:28 -0700 Subject: headers: separate linux/mod_devicetable.h from linux/platform_device.h At over 4000 #includes, is the 9th most #included header file in the Linux kernel. It does not need , so drop that header and explicitly add to source files that need it. 4146 #include After this patch, there are 225 files that use , for a reduction of around 3900 times that does not have to be read & parsed. 225 #include This patch was build-tested on 20 different arch-es. It also makes these drivers SubmitChecklist#1 compliant. Signed-off-by: Randy Dunlap Reported-by: kbuild test robot # drivers/media/platform/vimc/ Reported-by: kbuild test robot # drivers/pinctrl/pinctrl-u300.c Signed-off-by: Greg Kroah-Hartman --- arch/arm/mach-mmp/sram.c | 1 + drivers/ata/pata_imx.c | 1 + drivers/ata/pata_samsung_cf.c | 1 + drivers/auxdisplay/hd44780.c | 1 + drivers/char/hw_random/atmel-rng.c | 1 + drivers/char/hw_random/exynos-trng.c | 1 + drivers/char/hw_random/imx-rngc.c | 1 + drivers/char/hw_random/powernv-rng.c | 1 + drivers/crypto/mediatek/mtk-platform.c | 1 + drivers/crypto/qce/core.c | 1 + drivers/crypto/stm32/stm32_crc32.c | 1 + drivers/crypto/ux500/cryp/cryp_core.c | 1 + drivers/crypto/ux500/hash/hash_core.c | 1 + drivers/devfreq/tegra-devfreq.c | 1 + drivers/dma/ep93xx_dma.c | 1 + drivers/dma/s3c24xx-dma.c | 1 + drivers/extcon/extcon-intel-cht-wc.c | 1 + drivers/extcon/extcon-qcom-spmi-misc.c | 1 + drivers/gpu/drm/mediatek/mtk_cec.c | 1 + drivers/gpu/drm/sun4i/sun6i_drc.c | 1 + drivers/hsi/controllers/omap_ssi_port.c | 1 + drivers/hwmon/max197.c | 1 + drivers/hwmon/mc13783-adc.c | 1 + drivers/media/platform/coda/imx-vdoa.c | 1 + drivers/media/platform/rcar-fcp.c | 1 + drivers/media/platform/vimc/vimc-capture.c | 1 + drivers/media/platform/vimc/vimc-debayer.c | 1 + drivers/media/platform/vimc/vimc-scaler.c | 1 + drivers/media/platform/vimc/vimc-sensor.c | 1 + drivers/memory/tegra/tegra186.c | 1 + drivers/mfd/atmel-hlcdc.c | 1 + drivers/mfd/cros_ec_dev.c | 1 + drivers/mtd/nand/raw/brcmnand/brcmstb_nand.c | 1 + drivers/net/ethernet/calxeda/xgmac.c | 1 + drivers/net/ethernet/faraday/ftmac100.c | 1 + drivers/net/wireless/ath/ath9k/ahb.c | 1 + drivers/net/wireless/ti/wl12xx/main.c | 1 + drivers/net/wireless/ti/wl18xx/main.c | 1 + drivers/nvmem/lpc18xx_eeprom.c | 1 + drivers/nvmem/mtk-efuse.c | 1 + drivers/nvmem/qfprom.c | 1 + drivers/nvmem/uniphier-efuse.c | 1 + drivers/perf/arm-ccn.c | 1 + drivers/pinctrl/intel/pinctrl-merrifield.c | 1 + drivers/pinctrl/pinctrl-u300.c | 1 + drivers/pinctrl/sprd/pinctrl-sprd-sc9860.c | 1 + drivers/pinctrl/uniphier/pinctrl-uniphier-ld11.c | 1 + drivers/pinctrl/uniphier/pinctrl-uniphier-ld20.c | 1 + drivers/pinctrl/uniphier/pinctrl-uniphier-ld4.c | 1 + drivers/pinctrl/uniphier/pinctrl-uniphier-ld6b.c | 1 + drivers/pinctrl/uniphier/pinctrl-uniphier-pro4.c | 1 + drivers/pinctrl/uniphier/pinctrl-uniphier-pro5.c | 1 + drivers/pinctrl/uniphier/pinctrl-uniphier-pxs2.c | 1 + drivers/pinctrl/uniphier/pinctrl-uniphier-pxs3.c | 1 + drivers/pinctrl/uniphier/pinctrl-uniphier-sld8.c | 1 + drivers/platform/goldfish/goldfish_pipe.c | 1 + drivers/platform/x86/intel_bxtwc_tmu.c | 1 + drivers/power/avs/smartreflex.c | 1 + drivers/power/reset/ltc2952-poweroff.c | 1 + drivers/power/supply/max8998_charger.c | 1 + drivers/power/supply/olpc_battery.c | 1 + drivers/ptp/ptp_dte.c | 1 + drivers/regulator/tps65912-regulator.c | 1 + drivers/reset/reset-ath79.c | 1 + drivers/reset/reset-axs10x.c | 1 + drivers/reset/reset-imx7.c | 1 + drivers/rtc/rtc-coh901331.c | 1 + drivers/rtc/rtc-cpcap.c | 1 + drivers/rtc/rtc-ftrtc010.c | 1 + drivers/rtc/rtc-mc13xxx.c | 1 + drivers/rtc/rtc-mxc_v2.c | 1 + drivers/rtc/rtc-r7301.c | 1 + drivers/rtc/rtc-sh.c | 1 + drivers/rtc/rtc-tegra.c | 1 + drivers/siox/siox-bus-gpio.c | 1 + drivers/tty/goldfish.c | 1 + drivers/tty/serial/8250/8250_em.c | 1 + drivers/tty/serial/sccnxp.c | 1 + drivers/usb/gadget/udc/fsl_mxc_udc.c | 1 + drivers/video/fbdev/omap2/omapfb/displays/encoder-tpd12s015.c | 1 + drivers/w1/masters/mxc_w1.c | 1 + drivers/watchdog/coh901327_wdt.c | 1 + drivers/watchdog/davinci_wdt.c | 1 + drivers/watchdog/imgpdc_wdt.c | 1 + drivers/watchdog/max63xx_wdt.c | 1 + drivers/watchdog/max77620_wdt.c | 1 + drivers/watchdog/moxart_wdt.c | 1 + drivers/watchdog/omap_wdt.c | 1 + drivers/watchdog/tangox_wdt.c | 1 + include/linux/platform_device.h | 2 +- 90 files changed, 90 insertions(+), 1 deletion(-) diff --git a/arch/arm/mach-mmp/sram.c b/arch/arm/mach-mmp/sram.c index bf5e64906e65..ba91e4fe444d 100644 --- a/arch/arm/mach-mmp/sram.c +++ b/arch/arm/mach-mmp/sram.c @@ -15,6 +15,7 @@ */ #include +#include #include #include #include diff --git a/drivers/ata/pata_imx.c b/drivers/ata/pata_imx.c index d4caa23f5a88..6f0534047c6d 100644 --- a/drivers/ata/pata_imx.c +++ b/drivers/ata/pata_imx.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #define DRV_NAME "pata_imx" diff --git a/drivers/ata/pata_samsung_cf.c b/drivers/ata/pata_samsung_cf.c index bb96dc35950d..f5bd44b8bd63 100644 --- a/drivers/ata/pata_samsung_cf.c +++ b/drivers/ata/pata_samsung_cf.c @@ -17,6 +17,7 @@ #include #include +#include #include #include #include diff --git a/drivers/auxdisplay/hd44780.c b/drivers/auxdisplay/hd44780.c index 78d8f1986fec..f1a42f0f1ded 100644 --- a/drivers/auxdisplay/hd44780.c +++ b/drivers/auxdisplay/hd44780.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include diff --git a/drivers/char/hw_random/atmel-rng.c b/drivers/char/hw_random/atmel-rng.c index 661c82cde0f2..433426242b87 100644 --- a/drivers/char/hw_random/atmel-rng.c +++ b/drivers/char/hw_random/atmel-rng.c @@ -8,6 +8,7 @@ #include #include +#include #include #include #include diff --git a/drivers/char/hw_random/exynos-trng.c b/drivers/char/hw_random/exynos-trng.c index 1947aed7c044..94235761955c 100644 --- a/drivers/char/hw_random/exynos-trng.c +++ b/drivers/char/hw_random/exynos-trng.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include diff --git a/drivers/char/hw_random/imx-rngc.c b/drivers/char/hw_random/imx-rngc.c index 250123bc4905..14730be54edf 100644 --- a/drivers/char/hw_random/imx-rngc.c +++ b/drivers/char/hw_random/imx-rngc.c @@ -13,6 +13,7 @@ */ #include +#include #include #include #include diff --git a/drivers/char/hw_random/powernv-rng.c b/drivers/char/hw_random/powernv-rng.c index 263a5bb8e605..791182aa8e04 100644 --- a/drivers/char/hw_random/powernv-rng.c +++ b/drivers/char/hw_random/powernv-rng.c @@ -10,6 +10,7 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include +#include #include #include #include diff --git a/drivers/crypto/mediatek/mtk-platform.c b/drivers/crypto/mediatek/mtk-platform.c index b182e941b0cd..ee0404e27a0f 100644 --- a/drivers/crypto/mediatek/mtk-platform.c +++ b/drivers/crypto/mediatek/mtk-platform.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include "mtk-platform.h" diff --git a/drivers/crypto/qce/core.c b/drivers/crypto/qce/core.c index 718b32a3112e..1c3b36b75467 100644 --- a/drivers/crypto/qce/core.c +++ b/drivers/crypto/qce/core.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include diff --git a/drivers/crypto/stm32/stm32_crc32.c b/drivers/crypto/stm32/stm32_crc32.c index 8f09b8430893..a4a21fcf1e17 100644 --- a/drivers/crypto/stm32/stm32_crc32.c +++ b/drivers/crypto/stm32/stm32_crc32.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include diff --git a/drivers/crypto/ux500/cryp/cryp_core.c b/drivers/crypto/ux500/cryp/cryp_core.c index cb31b59c9d53..d2663a4e1f5e 100644 --- a/drivers/crypto/ux500/cryp/cryp_core.c +++ b/drivers/crypto/ux500/cryp/cryp_core.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include diff --git a/drivers/crypto/ux500/hash/hash_core.c b/drivers/crypto/ux500/hash/hash_core.c index 2d0a677bcc76..b141b74a084e 100644 --- a/drivers/crypto/ux500/hash/hash_core.c +++ b/drivers/crypto/ux500/hash/hash_core.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include diff --git a/drivers/devfreq/tegra-devfreq.c b/drivers/devfreq/tegra-devfreq.c index ae712159246f..c59d2eee5d30 100644 --- a/drivers/devfreq/tegra-devfreq.c +++ b/drivers/devfreq/tegra-devfreq.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include diff --git a/drivers/dma/ep93xx_dma.c b/drivers/dma/ep93xx_dma.c index ec240592f5c8..a15592383d4e 100644 --- a/drivers/dma/ep93xx_dma.c +++ b/drivers/dma/ep93xx_dma.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include diff --git a/drivers/dma/s3c24xx-dma.c b/drivers/dma/s3c24xx-dma.c index 7056fe7513b4..64744eb88720 100644 --- a/drivers/dma/s3c24xx-dma.c +++ b/drivers/dma/s3c24xx-dma.c @@ -35,6 +35,7 @@ #include #include #include +#include #include #include diff --git a/drivers/extcon/extcon-intel-cht-wc.c b/drivers/extcon/extcon-intel-cht-wc.c index b7e9ea377d70..5e1dd2772278 100644 --- a/drivers/extcon/extcon-intel-cht-wc.c +++ b/drivers/extcon/extcon-intel-cht-wc.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include diff --git a/drivers/extcon/extcon-qcom-spmi-misc.c b/drivers/extcon/extcon-qcom-spmi-misc.c index 660bbf163bf5..72bc0f2478e2 100644 --- a/drivers/extcon/extcon-qcom-spmi-misc.c +++ b/drivers/extcon/extcon-qcom-spmi-misc.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include diff --git a/drivers/gpu/drm/mediatek/mtk_cec.c b/drivers/gpu/drm/mediatek/mtk_cec.c index 7a3eb8c17ef9..5ce84d0dbf81 100644 --- a/drivers/gpu/drm/mediatek/mtk_cec.c +++ b/drivers/gpu/drm/mediatek/mtk_cec.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include "mtk_cec.h" diff --git a/drivers/gpu/drm/sun4i/sun6i_drc.c b/drivers/gpu/drm/sun4i/sun6i_drc.c index b5e071a49045..88eb268fdf73 100644 --- a/drivers/gpu/drm/sun4i/sun6i_drc.c +++ b/drivers/gpu/drm/sun4i/sun6i_drc.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include diff --git a/drivers/hsi/controllers/omap_ssi_port.c b/drivers/hsi/controllers/omap_ssi_port.c index 7765de2f1ef1..2ada82d2ec8c 100644 --- a/drivers/hsi/controllers/omap_ssi_port.c +++ b/drivers/hsi/controllers/omap_ssi_port.c @@ -20,6 +20,7 @@ * 02110-1301 USA */ +#include #include #include #include diff --git a/drivers/hwmon/max197.c b/drivers/hwmon/max197.c index 638567fb7cd8..3d9e210beedf 100644 --- a/drivers/hwmon/max197.c +++ b/drivers/hwmon/max197.c @@ -13,6 +13,7 @@ #include #include +#include #include #include #include diff --git a/drivers/hwmon/mc13783-adc.c b/drivers/hwmon/mc13783-adc.c index 67860ad2e3d9..78fe8759d2a9 100644 --- a/drivers/hwmon/mc13783-adc.c +++ b/drivers/hwmon/mc13783-adc.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include diff --git a/drivers/media/platform/coda/imx-vdoa.c b/drivers/media/platform/coda/imx-vdoa.c index 85a66e4e2f9a..96ab4b61669a 100644 --- a/drivers/media/platform/coda/imx-vdoa.c +++ b/drivers/media/platform/coda/imx-vdoa.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include diff --git a/drivers/media/platform/rcar-fcp.c b/drivers/media/platform/rcar-fcp.c index 2988031d285d..3ad9d0dc6fa7 100644 --- a/drivers/media/platform/rcar-fcp.c +++ b/drivers/media/platform/rcar-fcp.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include diff --git a/drivers/media/platform/vimc/vimc-capture.c b/drivers/media/platform/vimc/vimc-capture.c index 88a1e5670c72..ec68feaac378 100644 --- a/drivers/media/platform/vimc/vimc-capture.c +++ b/drivers/media/platform/vimc/vimc-capture.c @@ -17,6 +17,7 @@ #include #include +#include #include #include #include diff --git a/drivers/media/platform/vimc/vimc-debayer.c b/drivers/media/platform/vimc/vimc-debayer.c index 6e10b63ba9ec..77887f66f323 100644 --- a/drivers/media/platform/vimc/vimc-debayer.c +++ b/drivers/media/platform/vimc/vimc-debayer.c @@ -17,6 +17,7 @@ #include #include +#include #include #include #include diff --git a/drivers/media/platform/vimc/vimc-scaler.c b/drivers/media/platform/vimc/vimc-scaler.c index e583ec7a91da..b0952ee86296 100644 --- a/drivers/media/platform/vimc/vimc-scaler.c +++ b/drivers/media/platform/vimc/vimc-scaler.c @@ -17,6 +17,7 @@ #include #include +#include #include #include #include diff --git a/drivers/media/platform/vimc/vimc-sensor.c b/drivers/media/platform/vimc/vimc-sensor.c index 605e2a2d5dd5..b2b89315e7ba 100644 --- a/drivers/media/platform/vimc/vimc-sensor.c +++ b/drivers/media/platform/vimc/vimc-sensor.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include diff --git a/drivers/memory/tegra/tegra186.c b/drivers/memory/tegra/tegra186.c index 7254fb596979..ffda903c49bb 100644 --- a/drivers/memory/tegra/tegra186.c +++ b/drivers/memory/tegra/tegra186.c @@ -8,6 +8,7 @@ #include #include +#include #include #include diff --git a/drivers/mfd/atmel-hlcdc.c b/drivers/mfd/atmel-hlcdc.c index 4b15b0840f16..e82543bcfdc8 100644 --- a/drivers/mfd/atmel-hlcdc.c +++ b/drivers/mfd/atmel-hlcdc.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include diff --git a/drivers/mfd/cros_ec_dev.c b/drivers/mfd/cros_ec_dev.c index 306e1fd109bd..27af62ed480a 100644 --- a/drivers/mfd/cros_ec_dev.c +++ b/drivers/mfd/cros_ec_dev.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include diff --git a/drivers/mtd/nand/raw/brcmnand/brcmstb_nand.c b/drivers/mtd/nand/raw/brcmnand/brcmstb_nand.c index 5c271077ac87..489af7bc005a 100644 --- a/drivers/mtd/nand/raw/brcmnand/brcmstb_nand.c +++ b/drivers/mtd/nand/raw/brcmnand/brcmstb_nand.c @@ -13,6 +13,7 @@ #include #include +#include #include #include "brcmnand.h" diff --git a/drivers/net/ethernet/calxeda/xgmac.c b/drivers/net/ethernet/calxeda/xgmac.c index 2c63afff1382..13741ee49b9b 100644 --- a/drivers/net/ethernet/calxeda/xgmac.c +++ b/drivers/net/ethernet/calxeda/xgmac.c @@ -14,6 +14,7 @@ * this program. If not, see . */ #include +#include #include #include #include diff --git a/drivers/net/ethernet/faraday/ftmac100.c b/drivers/net/ethernet/faraday/ftmac100.c index aecc76504b69..a1197d3adbe0 100644 --- a/drivers/net/ethernet/faraday/ftmac100.c +++ b/drivers/net/ethernet/faraday/ftmac100.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include diff --git a/drivers/net/wireless/ath/ath9k/ahb.c b/drivers/net/wireless/ath/ath9k/ahb.c index 2bd982c3a479..63019c3de034 100644 --- a/drivers/net/wireless/ath/ath9k/ahb.c +++ b/drivers/net/wireless/ath/ath9k/ahb.c @@ -19,6 +19,7 @@ #include #include #include +#include #include "ath9k.h" static const struct platform_device_id ath9k_platform_id_table[] = { diff --git a/drivers/net/wireless/ti/wl12xx/main.c b/drivers/net/wireless/ti/wl12xx/main.c index 22009e14a8fc..4a4f797bb10f 100644 --- a/drivers/net/wireless/ti/wl12xx/main.c +++ b/drivers/net/wireless/ti/wl12xx/main.c @@ -20,6 +20,7 @@ */ #include +#include #include #include diff --git a/drivers/net/wireless/ti/wl18xx/main.c b/drivers/net/wireless/ti/wl18xx/main.c index ca0f936fc119..496b9b63cea1 100644 --- a/drivers/net/wireless/ti/wl18xx/main.c +++ b/drivers/net/wireless/ti/wl18xx/main.c @@ -20,6 +20,7 @@ */ #include +#include #include #include #include diff --git a/drivers/nvmem/lpc18xx_eeprom.c b/drivers/nvmem/lpc18xx_eeprom.c index b1af966206a6..a9534a6e8636 100644 --- a/drivers/nvmem/lpc18xx_eeprom.c +++ b/drivers/nvmem/lpc18xx_eeprom.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include diff --git a/drivers/nvmem/mtk-efuse.c b/drivers/nvmem/mtk-efuse.c index e66adf17a747..58c998b2e3bc 100644 --- a/drivers/nvmem/mtk-efuse.c +++ b/drivers/nvmem/mtk-efuse.c @@ -14,6 +14,7 @@ #include #include +#include #include #include #include diff --git a/drivers/nvmem/qfprom.c b/drivers/nvmem/qfprom.c index 4f650baad983..fbb1f1df6fc7 100644 --- a/drivers/nvmem/qfprom.c +++ b/drivers/nvmem/qfprom.c @@ -13,6 +13,7 @@ #include #include +#include #include #include #include diff --git a/drivers/nvmem/uniphier-efuse.c b/drivers/nvmem/uniphier-efuse.c index 271f0b2ff86a..286910336ef6 100644 --- a/drivers/nvmem/uniphier-efuse.c +++ b/drivers/nvmem/uniphier-efuse.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include diff --git a/drivers/perf/arm-ccn.c b/drivers/perf/arm-ccn.c index b416ee18e6bb..860f5bd8dce8 100644 --- a/drivers/perf/arm-ccn.c +++ b/drivers/perf/arm-ccn.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include diff --git a/drivers/pinctrl/intel/pinctrl-merrifield.c b/drivers/pinctrl/intel/pinctrl-merrifield.c index d9357054d41d..acea41073dfd 100644 --- a/drivers/pinctrl/intel/pinctrl-merrifield.c +++ b/drivers/pinctrl/intel/pinctrl-merrifield.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include diff --git a/drivers/pinctrl/pinctrl-u300.c b/drivers/pinctrl/pinctrl-u300.c index 9cc80a500880..2b1a61dba224 100644 --- a/drivers/pinctrl/pinctrl-u300.c +++ b/drivers/pinctrl/pinctrl-u300.c @@ -13,6 +13,7 @@ */ #include #include +#include #include #include #include diff --git a/drivers/pinctrl/sprd/pinctrl-sprd-sc9860.c b/drivers/pinctrl/sprd/pinctrl-sprd-sc9860.c index 3cdad8bc8f93..5702b6704137 100644 --- a/drivers/pinctrl/sprd/pinctrl-sprd-sc9860.c +++ b/drivers/pinctrl/sprd/pinctrl-sprd-sc9860.c @@ -13,6 +13,7 @@ */ #include +#include #include #include "pinctrl-sprd.h" diff --git a/drivers/pinctrl/uniphier/pinctrl-uniphier-ld11.c b/drivers/pinctrl/uniphier/pinctrl-uniphier-ld11.c index 58825f68b58b..c93752cd51c5 100644 --- a/drivers/pinctrl/uniphier/pinctrl-uniphier-ld11.c +++ b/drivers/pinctrl/uniphier/pinctrl-uniphier-ld11.c @@ -15,6 +15,7 @@ #include #include +#include #include #include diff --git a/drivers/pinctrl/uniphier/pinctrl-uniphier-ld20.c b/drivers/pinctrl/uniphier/pinctrl-uniphier-ld20.c index 9f449b35e300..130ce2484705 100644 --- a/drivers/pinctrl/uniphier/pinctrl-uniphier-ld20.c +++ b/drivers/pinctrl/uniphier/pinctrl-uniphier-ld20.c @@ -15,6 +15,7 @@ #include #include +#include #include #include diff --git a/drivers/pinctrl/uniphier/pinctrl-uniphier-ld4.c b/drivers/pinctrl/uniphier/pinctrl-uniphier-ld4.c index 0b10ebc07eb8..5a20121d2c93 100644 --- a/drivers/pinctrl/uniphier/pinctrl-uniphier-ld4.c +++ b/drivers/pinctrl/uniphier/pinctrl-uniphier-ld4.c @@ -15,6 +15,7 @@ #include #include +#include #include #include diff --git a/drivers/pinctrl/uniphier/pinctrl-uniphier-ld6b.c b/drivers/pinctrl/uniphier/pinctrl-uniphier-ld6b.c index 8e4d45fea885..76c57b0d5d88 100644 --- a/drivers/pinctrl/uniphier/pinctrl-uniphier-ld6b.c +++ b/drivers/pinctrl/uniphier/pinctrl-uniphier-ld6b.c @@ -15,6 +15,7 @@ #include #include +#include #include #include diff --git a/drivers/pinctrl/uniphier/pinctrl-uniphier-pro4.c b/drivers/pinctrl/uniphier/pinctrl-uniphier-pro4.c index 24788a74c254..4a0027373247 100644 --- a/drivers/pinctrl/uniphier/pinctrl-uniphier-pro4.c +++ b/drivers/pinctrl/uniphier/pinctrl-uniphier-pro4.c @@ -15,6 +15,7 @@ #include #include +#include #include #include diff --git a/drivers/pinctrl/uniphier/pinctrl-uniphier-pro5.c b/drivers/pinctrl/uniphier/pinctrl-uniphier-pro5.c index d5d5e579cb08..9a55972e9c16 100644 --- a/drivers/pinctrl/uniphier/pinctrl-uniphier-pro5.c +++ b/drivers/pinctrl/uniphier/pinctrl-uniphier-pro5.c @@ -15,6 +15,7 @@ #include #include +#include #include #include diff --git a/drivers/pinctrl/uniphier/pinctrl-uniphier-pxs2.c b/drivers/pinctrl/uniphier/pinctrl-uniphier-pxs2.c index 032619ad0e73..544f4b465308 100644 --- a/drivers/pinctrl/uniphier/pinctrl-uniphier-pxs2.c +++ b/drivers/pinctrl/uniphier/pinctrl-uniphier-pxs2.c @@ -15,6 +15,7 @@ #include #include +#include #include #include diff --git a/drivers/pinctrl/uniphier/pinctrl-uniphier-pxs3.c b/drivers/pinctrl/uniphier/pinctrl-uniphier-pxs3.c index 535bb2e935e4..9a15998b4f73 100644 --- a/drivers/pinctrl/uniphier/pinctrl-uniphier-pxs3.c +++ b/drivers/pinctrl/uniphier/pinctrl-uniphier-pxs3.c @@ -15,6 +15,7 @@ #include #include +#include #include #include diff --git a/drivers/pinctrl/uniphier/pinctrl-uniphier-sld8.c b/drivers/pinctrl/uniphier/pinctrl-uniphier-sld8.c index 0f921a653164..0402077d3b3a 100644 --- a/drivers/pinctrl/uniphier/pinctrl-uniphier-sld8.c +++ b/drivers/pinctrl/uniphier/pinctrl-uniphier-sld8.c @@ -15,6 +15,7 @@ #include #include +#include #include #include diff --git a/drivers/platform/goldfish/goldfish_pipe.c b/drivers/platform/goldfish/goldfish_pipe.c index 4866a8b12e98..2da567540c2d 100644 --- a/drivers/platform/goldfish/goldfish_pipe.c +++ b/drivers/platform/goldfish/goldfish_pipe.c @@ -48,6 +48,7 @@ #include +#include #include #include #include diff --git a/drivers/platform/x86/intel_bxtwc_tmu.c b/drivers/platform/x86/intel_bxtwc_tmu.c index ea865d4ca220..227943a20212 100644 --- a/drivers/platform/x86/intel_bxtwc_tmu.c +++ b/drivers/platform/x86/intel_bxtwc_tmu.c @@ -19,6 +19,7 @@ */ #include +#include #include #include #include diff --git a/drivers/power/avs/smartreflex.c b/drivers/power/avs/smartreflex.c index cb0237143dbe..1360a7fa542c 100644 --- a/drivers/power/avs/smartreflex.c +++ b/drivers/power/avs/smartreflex.c @@ -18,6 +18,7 @@ */ #include +#include #include #include #include diff --git a/drivers/power/reset/ltc2952-poweroff.c b/drivers/power/reset/ltc2952-poweroff.c index bfcd6fba6363..6b911b6b10a6 100644 --- a/drivers/power/reset/ltc2952-poweroff.c +++ b/drivers/power/reset/ltc2952-poweroff.c @@ -62,6 +62,7 @@ #include #include #include +#include #include #include diff --git a/drivers/power/supply/max8998_charger.c b/drivers/power/supply/max8998_charger.c index b64cf0f14142..cad7d1a8feec 100644 --- a/drivers/power/supply/max8998_charger.c +++ b/drivers/power/supply/max8998_charger.c @@ -21,6 +21,7 @@ #include #include +#include #include #include #include diff --git a/drivers/power/supply/olpc_battery.c b/drivers/power/supply/olpc_battery.c index 3bc2eea7b3b7..6da79ae14860 100644 --- a/drivers/power/supply/olpc_battery.c +++ b/drivers/power/supply/olpc_battery.c @@ -10,6 +10,7 @@ #include #include +#include #include #include #include diff --git a/drivers/ptp/ptp_dte.c b/drivers/ptp/ptp_dte.c index 6edd3b9c7f01..a7dc43368df4 100644 --- a/drivers/ptp/ptp_dte.c +++ b/drivers/ptp/ptp_dte.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include diff --git a/drivers/regulator/tps65912-regulator.c b/drivers/regulator/tps65912-regulator.c index a4921a70da55..276faeddc370 100644 --- a/drivers/regulator/tps65912-regulator.c +++ b/drivers/regulator/tps65912-regulator.c @@ -18,6 +18,7 @@ */ #include +#include #include #include diff --git a/drivers/reset/reset-ath79.c b/drivers/reset/reset-ath79.c index 2674880e5492..a7455916e396 100644 --- a/drivers/reset/reset-ath79.c +++ b/drivers/reset/reset-ath79.c @@ -17,6 +17,7 @@ #include #include +#include #include #include #include diff --git a/drivers/reset/reset-axs10x.c b/drivers/reset/reset-axs10x.c index afb298e46bd9..a854ef41364d 100644 --- a/drivers/reset/reset-axs10x.c +++ b/drivers/reset/reset-axs10x.c @@ -10,6 +10,7 @@ #include #include +#include #include #include diff --git a/drivers/reset/reset-imx7.c b/drivers/reset/reset-imx7.c index 4db177bc89bc..14bc78d28707 100644 --- a/drivers/reset/reset-imx7.c +++ b/drivers/reset/reset-imx7.c @@ -16,6 +16,7 @@ */ #include +#include #include #include #include diff --git a/drivers/rtc/rtc-coh901331.c b/drivers/rtc/rtc-coh901331.c index 2fc517498a5d..fc5cf5c44ae7 100644 --- a/drivers/rtc/rtc-coh901331.c +++ b/drivers/rtc/rtc-coh901331.c @@ -8,6 +8,7 @@ */ #include #include +#include #include #include #include diff --git a/drivers/rtc/rtc-cpcap.c b/drivers/rtc/rtc-cpcap.c index a8856f2b9bc2..6b477174a82f 100644 --- a/drivers/rtc/rtc-cpcap.c +++ b/drivers/rtc/rtc-cpcap.c @@ -24,6 +24,7 @@ */ #include #include +#include #include #include #include diff --git a/drivers/rtc/rtc-ftrtc010.c b/drivers/rtc/rtc-ftrtc010.c index 61f798c6101f..8f1dd88fa827 100644 --- a/drivers/rtc/rtc-ftrtc010.c +++ b/drivers/rtc/rtc-ftrtc010.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #define DRV_NAME "rtc-ftrtc010" diff --git a/drivers/rtc/rtc-mc13xxx.c b/drivers/rtc/rtc-mc13xxx.c index 1f892b238ddb..0fa33708fc49 100644 --- a/drivers/rtc/rtc-mc13xxx.c +++ b/drivers/rtc/rtc-mc13xxx.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include diff --git a/drivers/rtc/rtc-mxc_v2.c b/drivers/rtc/rtc-mxc_v2.c index c75f26dc8fcc..007879a5042d 100644 --- a/drivers/rtc/rtc-mxc_v2.c +++ b/drivers/rtc/rtc-mxc_v2.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include diff --git a/drivers/rtc/rtc-r7301.c b/drivers/rtc/rtc-r7301.c index 169704b2ce13..1943c8151152 100644 --- a/drivers/rtc/rtc-r7301.c +++ b/drivers/rtc/rtc-r7301.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include diff --git a/drivers/rtc/rtc-sh.c b/drivers/rtc/rtc-sh.c index 4f98543d1ea5..776b70a14e03 100644 --- a/drivers/rtc/rtc-sh.c +++ b/drivers/rtc/rtc-sh.c @@ -15,6 +15,7 @@ * for more details. */ #include +#include #include #include #include diff --git a/drivers/rtc/rtc-tegra.c b/drivers/rtc/rtc-tegra.c index 66efff60c4d5..8dc48fe7fc35 100644 --- a/drivers/rtc/rtc-tegra.c +++ b/drivers/rtc/rtc-tegra.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include diff --git a/drivers/siox/siox-bus-gpio.c b/drivers/siox/siox-bus-gpio.c index ea7ef982968b..46b4cda36bac 100644 --- a/drivers/siox/siox-bus-gpio.c +++ b/drivers/siox/siox-bus-gpio.c @@ -5,6 +5,7 @@ #include #include +#include #include #include diff --git a/drivers/tty/goldfish.c b/drivers/tty/goldfish.c index 37caba7c3aff..c8c5cdfc5e19 100644 --- a/drivers/tty/goldfish.c +++ b/drivers/tty/goldfish.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include diff --git a/drivers/tty/serial/8250/8250_em.c b/drivers/tty/serial/8250/8250_em.c index f6a86f2bc4e5..2a76e22d2ec0 100644 --- a/drivers/tty/serial/8250/8250_em.c +++ b/drivers/tty/serial/8250/8250_em.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include diff --git a/drivers/tty/serial/sccnxp.c b/drivers/tty/serial/sccnxp.c index d6ae3086c2a2..339befdd2f4d 100644 --- a/drivers/tty/serial/sccnxp.c +++ b/drivers/tty/serial/sccnxp.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include diff --git a/drivers/usb/gadget/udc/fsl_mxc_udc.c b/drivers/usb/gadget/udc/fsl_mxc_udc.c index f29cf5c6160c..5a321992decc 100644 --- a/drivers/usb/gadget/udc/fsl_mxc_udc.c +++ b/drivers/usb/gadget/udc/fsl_mxc_udc.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include diff --git a/drivers/video/fbdev/omap2/omapfb/displays/encoder-tpd12s015.c b/drivers/video/fbdev/omap2/omapfb/displays/encoder-tpd12s015.c index 80dc47347e21..3079a3df8c37 100644 --- a/drivers/video/fbdev/omap2/omapfb/displays/encoder-tpd12s015.c +++ b/drivers/video/fbdev/omap2/omapfb/displays/encoder-tpd12s015.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include diff --git a/drivers/w1/masters/mxc_w1.c b/drivers/w1/masters/mxc_w1.c index 8851d441e5fd..50b46c4399ea 100644 --- a/drivers/w1/masters/mxc_w1.c +++ b/drivers/w1/masters/mxc_w1.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include diff --git a/drivers/watchdog/coh901327_wdt.c b/drivers/watchdog/coh901327_wdt.c index e3a78f927f83..f29d1edc5bad 100644 --- a/drivers/watchdog/coh901327_wdt.c +++ b/drivers/watchdog/coh901327_wdt.c @@ -7,6 +7,7 @@ * Author: Linus Walleij */ #include +#include #include #include #include diff --git a/drivers/watchdog/davinci_wdt.c b/drivers/watchdog/davinci_wdt.c index 6c6594261cb7..ebb85d60b6d5 100644 --- a/drivers/watchdog/davinci_wdt.c +++ b/drivers/watchdog/davinci_wdt.c @@ -13,6 +13,7 @@ #include #include +#include #include #include #include diff --git a/drivers/watchdog/imgpdc_wdt.c b/drivers/watchdog/imgpdc_wdt.c index 6ed39dee995f..a3134ffa59f8 100644 --- a/drivers/watchdog/imgpdc_wdt.c +++ b/drivers/watchdog/imgpdc_wdt.c @@ -44,6 +44,7 @@ #include #include #include +#include #include #include #include diff --git a/drivers/watchdog/max63xx_wdt.c b/drivers/watchdog/max63xx_wdt.c index ac5840d9689a..bf6a068245ba 100644 --- a/drivers/watchdog/max63xx_wdt.c +++ b/drivers/watchdog/max63xx_wdt.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include diff --git a/drivers/watchdog/max77620_wdt.c b/drivers/watchdog/max77620_wdt.c index 2c9f53eaff4f..70c9cd3ba938 100644 --- a/drivers/watchdog/max77620_wdt.c +++ b/drivers/watchdog/max77620_wdt.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include diff --git a/drivers/watchdog/moxart_wdt.c b/drivers/watchdog/moxart_wdt.c index 2c4a73d1e214..430c3ab84c07 100644 --- a/drivers/watchdog/moxart_wdt.c +++ b/drivers/watchdog/moxart_wdt.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include diff --git a/drivers/watchdog/omap_wdt.c b/drivers/watchdog/omap_wdt.c index ae77112ce97f..cbd752f9ac56 100644 --- a/drivers/watchdog/omap_wdt.c +++ b/drivers/watchdog/omap_wdt.c @@ -29,6 +29,7 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include +#include #include #include #include diff --git a/drivers/watchdog/tangox_wdt.c b/drivers/watchdog/tangox_wdt.c index b1de8297fa40..d0b53f3c0d17 100644 --- a/drivers/watchdog/tangox_wdt.c +++ b/drivers/watchdog/tangox_wdt.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include diff --git a/include/linux/platform_device.h b/include/linux/platform_device.h index 3097c943fab9..1a9f38f27f65 100644 --- a/include/linux/platform_device.h +++ b/include/linux/platform_device.h @@ -12,13 +12,13 @@ #define _PLATFORM_DEVICE_H_ #include -#include #define PLATFORM_DEVID_NONE (-1) #define PLATFORM_DEVID_AUTO (-2) struct mfd_cell; struct property_entry; +struct platform_device_id; struct platform_device { const char *name; -- cgit v1.2.3 From 053a389b461c558e4ecf84a55b94dfc51cb0cbce Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Sat, 7 Jul 2018 14:03:29 -0700 Subject: headers: fix build error in arch/arm/plat-samsung/adc.c, add arch/arm/plat-samsung/adc.c needs to #include explicitly since that header file was removed from (it wasn't needed there). Reported-by: kbuild test robot Signed-off-by: Randy Dunlap Signed-off-by: Greg Kroah-Hartman --- arch/arm/plat-samsung/adc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm/plat-samsung/adc.c b/arch/arm/plat-samsung/adc.c index 2da35735fa38..ee3d5c989a76 100644 --- a/arch/arm/plat-samsung/adc.c +++ b/arch/arm/plat-samsung/adc.c @@ -8,6 +8,7 @@ #include #include +#include #include #include #include -- cgit v1.2.3 From e464d28273f895bfc57bfec2beb8a894eb45237c Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Sat, 7 Jul 2018 20:28:41 +0100 Subject: slimbus: stream: Fix htmldocs warnings This patch fixes below warning during building htmldoc: slimbus.h:352: warning: Function parameter or member 'name' not described in 'slim_stream_runtime' This patch also removes documentation for state variable in struct slim_stream_runtime which was redundant and removed. Reported-by: kbuild test robot Signed-off-by: Srinivas Kandagatla Signed-off-by: Greg Kroah-Hartman --- drivers/slimbus/slimbus.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/slimbus/slimbus.h b/drivers/slimbus/slimbus.h index 6dbc2b320704..4399d1873e2d 100644 --- a/drivers/slimbus/slimbus.h +++ b/drivers/slimbus/slimbus.h @@ -325,9 +325,8 @@ enum slim_transport_protocol { /** * struct slim_stream_runtime - SLIMbus stream runtime instance * - * @dev: Name of the stream + * @name: Name of the stream * @dev: SLIM Device instance associated with this stream - * @state: state of stream * @direction: direction of stream * @prot: Transport protocol used in this stream * @rate: Data rate of samples * -- cgit v1.2.3 From 81b18bce48af3e222ca9eebb3b931f59141b7b49 Mon Sep 17 00:00:00 2001 From: Sunil Muthuswamy Date: Sun, 8 Jul 2018 02:56:51 +0000 Subject: Drivers: HV: Send one page worth of kmsg dump over Hyper-V during panic In the VM mode on Hyper-V, currently, when the kernel panics, an error code and few register values are populated in an MSR and the Hypervisor notified. This information is collected on the host. The amount of information currently collected is found to be limited and not very actionable. To gather more actionable data, such as stack trace, the proposal is to write one page worth of kmsg data on an allocated page and the Hypervisor notified of the page address through the MSR. - Sysctl option to control the behavior, with ON by default. Cc: K. Y. Srinivasan Cc: Stephen Hemminger Signed-off-by: Sunil Muthuswamy Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman --- Documentation/sysctl/kernel.txt | 11 ++++ arch/x86/hyperv/hv_init.c | 27 +++++++++ arch/x86/include/asm/hyperv-tlfs.h | 5 +- arch/x86/include/asm/mshyperv.h | 1 + drivers/hv/vmbus_drv.c | 110 +++++++++++++++++++++++++++++++++++++ 5 files changed, 152 insertions(+), 2 deletions(-) diff --git a/Documentation/sysctl/kernel.txt b/Documentation/sysctl/kernel.txt index eded671d55eb..59585030cbaf 100644 --- a/Documentation/sysctl/kernel.txt +++ b/Documentation/sysctl/kernel.txt @@ -39,6 +39,7 @@ show up in /proc/sys/kernel: - hung_task_check_count - hung_task_timeout_secs - hung_task_warnings +- hyperv_record_panic_msg - kexec_load_disabled - kptr_restrict - l2cr [ PPC only ] @@ -374,6 +375,16 @@ This file shows up if CONFIG_DETECT_HUNG_TASK is enabled. ============================================================== +hyperv_record_panic_msg: + +Controls whether the panic kmsg data should be reported to Hyper-V. + +0: do not report panic kmsg data. + +1: report the panic kmsg data. This is the default behavior. + +============================================================== + kexec_load_disabled: A toggle indicating if the kexec_load syscall has been disabled. This diff --git a/arch/x86/hyperv/hv_init.c b/arch/x86/hyperv/hv_init.c index 595e44e8abaa..9c70018a9906 100644 --- a/arch/x86/hyperv/hv_init.c +++ b/arch/x86/hyperv/hv_init.c @@ -423,6 +423,33 @@ void hyperv_report_panic(struct pt_regs *regs, long err) } EXPORT_SYMBOL_GPL(hyperv_report_panic); +/** + * hyperv_report_panic_msg - report panic message to Hyper-V + * @pa: physical address of the panic page containing the message + * @size: size of the message in the page + */ +void hyperv_report_panic_msg(phys_addr_t pa, size_t size) +{ + /* + * P3 to contain the physical address of the panic page & P4 to + * contain the size of the panic data in that page. Rest of the + * registers are no-op when the NOTIFY_MSG flag is set. + */ + wrmsrl(HV_X64_MSR_CRASH_P0, 0); + wrmsrl(HV_X64_MSR_CRASH_P1, 0); + wrmsrl(HV_X64_MSR_CRASH_P2, 0); + wrmsrl(HV_X64_MSR_CRASH_P3, pa); + wrmsrl(HV_X64_MSR_CRASH_P4, size); + + /* + * Let Hyper-V know there is crash data available along with + * the panic message. + */ + wrmsrl(HV_X64_MSR_CRASH_CTL, + (HV_CRASH_CTL_CRASH_NOTIFY | HV_CRASH_CTL_CRASH_NOTIFY_MSG)); +} +EXPORT_SYMBOL_GPL(hyperv_report_panic_msg); + bool hv_is_hyperv_initialized(void) { union hv_x64_msr_hypercall_contents hypercall_msr; diff --git a/arch/x86/include/asm/hyperv-tlfs.h b/arch/x86/include/asm/hyperv-tlfs.h index 96272e99b64e..6ced78af48da 100644 --- a/arch/x86/include/asm/hyperv-tlfs.h +++ b/arch/x86/include/asm/hyperv-tlfs.h @@ -176,9 +176,10 @@ #define HV_X64_ENLIGHTENED_VMCS_RECOMMENDED (1 << 14) /* - * Crash notification flag. + * Crash notification flags. */ -#define HV_CRASH_CTL_CRASH_NOTIFY (1ULL << 63) +#define HV_CRASH_CTL_CRASH_NOTIFY_MSG BIT_ULL(62) +#define HV_CRASH_CTL_CRASH_NOTIFY BIT_ULL(63) /* MSR used to identify the guest OS. */ #define HV_X64_MSR_GUEST_OS_ID 0x40000000 diff --git a/arch/x86/include/asm/mshyperv.h b/arch/x86/include/asm/mshyperv.h index 81e768b8d9eb..156a2e5a97a9 100644 --- a/arch/x86/include/asm/mshyperv.h +++ b/arch/x86/include/asm/mshyperv.h @@ -299,6 +299,7 @@ static inline int cpumask_to_vpset(struct hv_vpset *vpset, void __init hyperv_init(void); void hyperv_setup_mmu_ops(void); void hyperv_report_panic(struct pt_regs *regs, long err); +void hyperv_report_panic_msg(phys_addr_t pa, size_t size); bool hv_is_hyperv_initialized(void); void hyperv_cleanup(void); diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c index b10fe26c4891..05e37283d7c3 100644 --- a/drivers/hv/vmbus_drv.c +++ b/drivers/hv/vmbus_drv.c @@ -56,6 +56,8 @@ static struct completion probe_event; static int hyperv_cpuhp_online; +static void *hv_panic_page; + static int hyperv_panic_event(struct notifier_block *nb, unsigned long val, void *args) { @@ -1018,6 +1020,75 @@ static void vmbus_isr(void) add_interrupt_randomness(HYPERVISOR_CALLBACK_VECTOR, 0); } +/* + * Boolean to control whether to report panic messages over Hyper-V. + * + * It can be set via /proc/sys/kernel/hyperv/record_panic_msg + */ +static int sysctl_record_panic_msg = 1; + +/* + * Callback from kmsg_dump. Grab as much as possible from the end of the kmsg + * buffer and call into Hyper-V to transfer the data. + */ +static void hv_kmsg_dump(struct kmsg_dumper *dumper, + enum kmsg_dump_reason reason) +{ + size_t bytes_written; + phys_addr_t panic_pa; + + /* We are only interested in panics. */ + if ((reason != KMSG_DUMP_PANIC) || (!sysctl_record_panic_msg)) + return; + + panic_pa = virt_to_phys(hv_panic_page); + + /* + * Write dump contents to the page. No need to synchronize; panic should + * be single-threaded. + */ + if (!kmsg_dump_get_buffer(dumper, true, hv_panic_page, + PAGE_SIZE, &bytes_written)) { + pr_err("Hyper-V: Unable to get kmsg data for panic\n"); + return; + } + + hyperv_report_panic_msg(panic_pa, bytes_written); +} + +static struct kmsg_dumper hv_kmsg_dumper = { + .dump = hv_kmsg_dump, +}; + +static struct ctl_table_header *hv_ctl_table_hdr; +static int zero; +static int one = 1; + +/* + * sysctl option to allow the user to control whether kmsg data should be + * reported to Hyper-V on panic. + */ +static struct ctl_table hv_ctl_table[] = { + { + .procname = "hyperv_record_panic_msg", + .data = &sysctl_record_panic_msg, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = proc_dointvec_minmax, + .extra1 = &zero, + .extra2 = &one + }, + {} +}; + +static struct ctl_table hv_root_table[] = { + { + .procname = "kernel", + .mode = 0555, + .child = hv_ctl_table + }, + {} +}; /* * vmbus_bus_init -Main vmbus driver initialization routine. @@ -1065,6 +1136,32 @@ static int vmbus_bus_init(void) * Only register if the crash MSRs are available */ if (ms_hyperv.misc_features & HV_FEATURE_GUEST_CRASH_MSR_AVAILABLE) { + u64 hyperv_crash_ctl; + /* + * Sysctl registration is not fatal, since by default + * reporting is enabled. + */ + hv_ctl_table_hdr = register_sysctl_table(hv_root_table); + if (!hv_ctl_table_hdr) + pr_err("Hyper-V: sysctl table register error"); + + /* + * Register for panic kmsg callback only if the right + * capability is supported by the hypervisor. + */ + rdmsrl(HV_X64_MSR_CRASH_CTL, hyperv_crash_ctl); + if (hyperv_crash_ctl & HV_CRASH_CTL_CRASH_NOTIFY_MSG) { + hv_panic_page = (void *)get_zeroed_page(GFP_KERNEL); + if (hv_panic_page) { + ret = kmsg_dump_register(&hv_kmsg_dumper); + if (ret) + pr_err("Hyper-V: kmsg dump register " + "error 0x%x\n", ret); + } else + pr_err("Hyper-V: panic message page memory " + "allocation failed"); + } + register_die_notifier(&hyperv_die_block); atomic_notifier_chain_register(&panic_notifier_list, &hyperv_panic_block); @@ -1081,6 +1178,11 @@ err_alloc: hv_remove_vmbus_irq(); bus_unregister(&hv_bus); + free_page((unsigned long)hv_panic_page); + if (!hv_ctl_table_hdr) { + unregister_sysctl_table(hv_ctl_table_hdr); + hv_ctl_table_hdr = NULL; + } return ret; } @@ -1785,10 +1887,18 @@ static void __exit vmbus_exit(void) vmbus_free_channels(); if (ms_hyperv.misc_features & HV_FEATURE_GUEST_CRASH_MSR_AVAILABLE) { + kmsg_dump_unregister(&hv_kmsg_dumper); unregister_die_notifier(&hyperv_die_block); atomic_notifier_chain_unregister(&panic_notifier_list, &hyperv_panic_block); } + + free_page((unsigned long)hv_panic_page); + if (!hv_ctl_table_hdr) { + unregister_sysctl_table(hv_ctl_table_hdr); + hv_ctl_table_hdr = NULL; + } + bus_unregister(&hv_bus); cpuhp_remove_state(hyperv_cpuhp_online); -- cgit v1.2.3 From 37070d6c940e582a39fd9287e06bb40b9f336184 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Mon, 9 Jul 2018 17:19:02 +0200 Subject: headers: fix linux/mod_devicetable.h inclusions A couple of drivers produced build errors after the mod_devicetable.h header was split out from the platform_device one, e.g. drivers/media/platform/davinci/vpbe_osd.c:42:40: error: array type has incomplete element type 'struct platform_device_id' drivers/media/platform/davinci/vpbe_venc.c:42:40: error: array type has incomplete element type 'struct platform_device_id' This adds the inclusion where needed. Fixes: ac3167257b9f ("headers: separate linux/mod_devicetable.h from linux/platform_device.h") Signed-off-by: Arnd Bergmann Acked-by: Michael S. Tsirkin Signed-off-by: Greg Kroah-Hartman --- drivers/firmware/qemu_fw_cfg.c | 1 + drivers/media/platform/davinci/vpbe_osd.c | 1 + drivers/media/platform/davinci/vpbe_venc.c | 1 + drivers/media/platform/qcom/venus/vdec.c | 1 + drivers/media/platform/qcom/venus/venc.c | 1 + drivers/media/platform/sti/hva/hva-v4l2.c | 1 + drivers/platform/x86/intel_punit_ipc.c | 1 + 7 files changed, 7 insertions(+) diff --git a/drivers/firmware/qemu_fw_cfg.c b/drivers/firmware/qemu_fw_cfg.c index 14fedbeca724..039e0f91dba8 100644 --- a/drivers/firmware/qemu_fw_cfg.c +++ b/drivers/firmware/qemu_fw_cfg.c @@ -28,6 +28,7 @@ */ #include +#include #include #include #include diff --git a/drivers/media/platform/davinci/vpbe_osd.c b/drivers/media/platform/davinci/vpbe_osd.c index 7f610320426d..c551a25d90d9 100644 --- a/drivers/media/platform/davinci/vpbe_osd.c +++ b/drivers/media/platform/davinci/vpbe_osd.c @@ -18,6 +18,7 @@ * */ #include +#include #include #include #include diff --git a/drivers/media/platform/davinci/vpbe_venc.c b/drivers/media/platform/davinci/vpbe_venc.c index ba157827192c..ddcad7b3e76c 100644 --- a/drivers/media/platform/davinci/vpbe_venc.c +++ b/drivers/media/platform/davinci/vpbe_venc.c @@ -11,6 +11,7 @@ * GNU General Public License for more details. */ #include +#include #include #include #include diff --git a/drivers/media/platform/qcom/venus/vdec.c b/drivers/media/platform/qcom/venus/vdec.c index 49bbd1861d3a..d1101e1decaa 100644 --- a/drivers/media/platform/qcom/venus/vdec.c +++ b/drivers/media/platform/qcom/venus/vdec.c @@ -14,6 +14,7 @@ */ #include #include +#include #include #include #include diff --git a/drivers/media/platform/qcom/venus/venc.c b/drivers/media/platform/qcom/venus/venc.c index 6b2ce479584e..7e8553c52321 100644 --- a/drivers/media/platform/qcom/venus/venc.c +++ b/drivers/media/platform/qcom/venus/venc.c @@ -14,6 +14,7 @@ */ #include #include +#include #include #include #include diff --git a/drivers/media/platform/sti/hva/hva-v4l2.c b/drivers/media/platform/sti/hva/hva-v4l2.c index 15080cb00fa7..5a807c7c5e79 100644 --- a/drivers/media/platform/sti/hva/hva-v4l2.c +++ b/drivers/media/platform/sti/hva/hva-v4l2.c @@ -6,6 +6,7 @@ */ #include +#include #include #include #include diff --git a/drivers/platform/x86/intel_punit_ipc.c b/drivers/platform/x86/intel_punit_ipc.c index b5b890127479..f1afc0ebbc68 100644 --- a/drivers/platform/x86/intel_punit_ipc.c +++ b/drivers/platform/x86/intel_punit_ipc.c @@ -12,6 +12,7 @@ */ #include +#include #include #include #include -- cgit v1.2.3 From c9c159b22902f94ca68e29e4b95b666b87577539 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Wed, 11 Jul 2018 17:50:29 +0200 Subject: extcon: max3355: include mod_devicetable.h Another driver turned up that is missing linux/mod_devicetable.h after the device IDs are split out from linux/platform_device.h: drivers/extcon/extcon-max3355.c:127:34: error: array type has incomplete element type 'struct of_device_id' static const struct of_device_id max3355_match_table[] = { Fixes: ac3167257b9f ("headers: separate linux/mod_devicetable.h from linux/platform_device.h") Signed-off-by: Arnd Bergmann Signed-off-by: Greg Kroah-Hartman --- drivers/extcon/extcon-max3355.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/extcon/extcon-max3355.c b/drivers/extcon/extcon-max3355.c index 0aa410836f4e..1335a476bfec 100644 --- a/drivers/extcon/extcon-max3355.c +++ b/drivers/extcon/extcon-max3355.c @@ -14,6 +14,7 @@ #include #include #include +#include #include struct max3355_data { -- cgit v1.2.3 From c355bb1afc5fe59e559fff585204180a94c6d58c Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Tue, 10 Apr 2018 14:43:02 +0200 Subject: extcon: int3496: Use proper GPIO include Since commit eca0f13c836a ("extcon: int3496: Ignore incorrect IoRestriction for ID pin"), the driver doesn't use GPIOF_* flags anymore. We can thus now drop the deprecated include file for GPIO and use the new one. Signed-off-by: Wolfram Sang Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon-intel-int3496.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/extcon/extcon-intel-int3496.c b/drivers/extcon/extcon-intel-int3496.c index acaccb128fc4..fd24debe58a3 100644 --- a/drivers/extcon/extcon-intel-int3496.c +++ b/drivers/extcon/extcon-intel-int3496.c @@ -20,7 +20,7 @@ #include #include -#include +#include #include #include #include -- cgit v1.2.3 From 686b30580058fa546035537bb4898b8ac3ae9561 Mon Sep 17 00:00:00 2001 From: Enric Balletbo i Serra Date: Wed, 6 Jun 2018 17:08:55 +0200 Subject: extcon: usbc-cros-ec: Make license text and module license match. The license text is specifying "GPLv2" but the MODULE_LICENSE is set to GPL which means GNU Public License v2 or later. When MODULE_LICENSE and boiler plate does not match, go for boiler plate license. Signed-off-by: Enric Balletbo i Serra Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon-usbc-cros-ec.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/extcon/extcon-usbc-cros-ec.c b/drivers/extcon/extcon-usbc-cros-ec.c index 6721ab01fe7d..a5b75a12cfc9 100644 --- a/drivers/extcon/extcon-usbc-cros-ec.c +++ b/drivers/extcon/extcon-usbc-cros-ec.c @@ -548,4 +548,4 @@ module_platform_driver(extcon_cros_ec_driver); MODULE_DESCRIPTION("ChromeOS Embedded Controller extcon driver"); MODULE_AUTHOR("Benson Leung "); -MODULE_LICENSE("GPL"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From 11bab5a28d4cb22f816beaed697ebf715730f96a Mon Sep 17 00:00:00 2001 From: Enric Balletbo i Serra Date: Wed, 6 Jun 2018 17:08:56 +0200 Subject: extcon: usbc-cros-ec: Switch to SPDX identifier. Adopt the SPDX license identifier headers to ease license compliance management. Signed-off-by: Enric Balletbo i Serra Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon-usbc-cros-ec.c | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/drivers/extcon/extcon-usbc-cros-ec.c b/drivers/extcon/extcon-usbc-cros-ec.c index a5b75a12cfc9..43c0a936ab82 100644 --- a/drivers/extcon/extcon-usbc-cros-ec.c +++ b/drivers/extcon/extcon-usbc-cros-ec.c @@ -1,18 +1,8 @@ -/** - * drivers/extcon/extcon-usbc-cros-ec - ChromeOS Embedded Controller extcon - * - * Copyright (C) 2017 Google, Inc - * Author: Benson Leung - * - * 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. - */ +// SPDX-License-Identifier: GPL-2.0 +// ChromeOS Embedded Controller extcon +// +// Copyright (C) 2017 Google, Inc. +// Author: Benson Leung #include #include -- cgit v1.2.3 From 8a9dbb779fe882325b9a0238494a7afaff2eb444 Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Thu, 14 Jun 2018 11:16:29 +0900 Subject: extcon: Release locking when sending the notification of connector state Previously, extcon used the spinlock before calling the notifier_call_chain to prevent the scheduled out of task and to prevent the notification delay. When spinlock is locked for sending the notification, deadlock issue occured on the side of extcon consumer device. To fix this issue, extcon consumer device should always use the work. it is always not reasonable to use work. To fix this issue on extcon consumer device, release locking when sending the notification of connector state. Fixes: ab11af049f88 ("extcon: Add the synchronization extcon APIs to support the notification") Cc: stable@vger.kernel.org Cc: Roger Quadros Cc: Kishon Vijay Abraham I Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/extcon/extcon.c b/drivers/extcon/extcon.c index af83ad58819c..b9d27c8fe57e 100644 --- a/drivers/extcon/extcon.c +++ b/drivers/extcon/extcon.c @@ -433,8 +433,8 @@ int extcon_sync(struct extcon_dev *edev, unsigned int id) return index; spin_lock_irqsave(&edev->lock, flags); - state = !!(edev->state & BIT(index)); + spin_unlock_irqrestore(&edev->lock, flags); /* * Call functions in a raw notifier chain for the specific one @@ -448,6 +448,7 @@ int extcon_sync(struct extcon_dev *edev, unsigned int id) */ raw_notifier_call_chain(&edev->nh_all, state, edev); + spin_lock_irqsave(&edev->lock, flags); /* This could be in interrupt handler */ prop_buf = (char *)get_zeroed_page(GFP_ATOMIC); if (!prop_buf) { -- cgit v1.2.3 From c00bac88764a0f294d34b40e5f4987de8ceab711 Mon Sep 17 00:00:00 2001 From: Eddie James Date: Tue, 3 Jul 2018 16:21:29 -0500 Subject: fsi: sbefifo: Add missing mutex_unlock There was no unlock of the FFDC mutex. Fixes: 9f4a8a2d7f9d ("fsi/sbefifo: Add driver for the SBE FIFO") Signed-off-by: Eddie James Signed-off-by: Benjamin Herrenschmidt --- drivers/fsi/fsi-sbefifo.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/fsi/fsi-sbefifo.c b/drivers/fsi/fsi-sbefifo.c index 4f076fd2939c..a34ff997ca9d 100644 --- a/drivers/fsi/fsi-sbefifo.c +++ b/drivers/fsi/fsi-sbefifo.c @@ -194,6 +194,7 @@ static void sbefifo_dump_ffdc(struct device *dev, const __be32 *ffdc, } dev_warn(dev, "+-------------------------------------------+\n"); } + mutex_unlock(&sbefifo_ffdc_mutex); } int sbefifo_parse_status(struct device *dev, u16 cmd, __be32 *response, -- cgit v1.2.3 From 32f7f89d3065d950a3524c91f4031acc129195cc Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Tue, 3 Jul 2018 06:43:49 -0700 Subject: fsi/sbefifo: Add dependency on OF_ADDRESS The driver calls of_platform_device_create() which is only available if OF_ADDRESS is enabled. When building sparc64 images, this results in ERROR: "of_platform_device_create" [drivers/fsi/fsi-sbefifo.ko] undefined! Fixes: 9f4a8a2d7f9d ("fsi/sbefifo: Add driver for the SBE FIFO") Cc: Benjamin Herrenschmidt Signed-off-by: Guenter Roeck Signed-off-by: Benjamin Herrenschmidt --- drivers/fsi/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/fsi/Kconfig b/drivers/fsi/Kconfig index 24f84a96b8b9..9c08f467a7bb 100644 --- a/drivers/fsi/Kconfig +++ b/drivers/fsi/Kconfig @@ -34,6 +34,7 @@ config FSI_SCOM config FSI_SBEFIFO tristate "SBEFIFO FSI client device driver" + depends on OF_ADDRESS ---help--- This option enables an FSI based SBEFIFO device driver. The SBEFIFO is a pipe-like FSI device for communicating with the self boot engine -- cgit v1.2.3 From d5c66e61e7fee82c50c04af8b69c1b88ce173b95 Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Thu, 12 Jul 2018 11:53:37 +1000 Subject: fsi: sbefifo: Fix checker warning about late NULL check "dev" is dereferences before it's checked. Reported-by: Dan Carpenter Signed-off-by: Benjamin Herrenschmidt --- drivers/fsi/fsi-sbefifo.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/fsi/fsi-sbefifo.c b/drivers/fsi/fsi-sbefifo.c index a34ff997ca9d..6b31cc24fb0d 100644 --- a/drivers/fsi/fsi-sbefifo.c +++ b/drivers/fsi/fsi-sbefifo.c @@ -706,13 +706,16 @@ static int __sbefifo_submit(struct sbefifo *sbefifo, int sbefifo_submit(struct device *dev, const __be32 *command, size_t cmd_len, __be32 *response, size_t *resp_len) { - struct sbefifo *sbefifo = dev_get_drvdata(dev); + struct sbefifo *sbefifo; struct iov_iter resp_iter; struct kvec resp_iov; size_t rbytes; int rc; - if (!dev || !sbefifo) + if (!dev) + return -ENODEV; + sbefifo = dev_get_drvdata(dev); + if (!sbefifo) return -ENODEV; if (WARN_ON_ONCE(sbefifo->magic != SBEFIFO_MAGIC)) return -ENODEV; -- cgit v1.2.3 From 935f9636389f0acd96e6ebcbc7d97425b84163b5 Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Tue, 29 May 2018 14:44:08 +1000 Subject: fsi: Move code around to avoid forward declaration Move fsi_slave_set_smode() and its helpers to before it's first user and remove the corresponding forward declaration. Signed-off-by: Benjamin Herrenschmidt Reviewed-by: Joel Stanley --- drivers/fsi/fsi-core.c | 94 ++++++++++++++++++++++++-------------------------- 1 file changed, 46 insertions(+), 48 deletions(-) diff --git a/drivers/fsi/fsi-core.c b/drivers/fsi/fsi-core.c index a3f0d41f4c59..f039b348e484 100644 --- a/drivers/fsi/fsi-core.c +++ b/drivers/fsi/fsi-core.c @@ -215,7 +215,52 @@ static int fsi_slave_report_and_clear_errors(struct fsi_slave *slave) &irq, sizeof(irq)); } -static int fsi_slave_set_smode(struct fsi_master *master, int link, int id); +/* Encode slave local bus echo delay */ +static inline uint32_t fsi_smode_echodly(int x) +{ + return (x & FSI_SMODE_ED_MASK) << FSI_SMODE_ED_SHIFT; +} + +/* Encode slave local bus send delay */ +static inline uint32_t fsi_smode_senddly(int x) +{ + return (x & FSI_SMODE_SD_MASK) << FSI_SMODE_SD_SHIFT; +} + +/* Encode slave local bus clock rate ratio */ +static inline uint32_t fsi_smode_lbcrr(int x) +{ + return (x & FSI_SMODE_LBCRR_MASK) << FSI_SMODE_LBCRR_SHIFT; +} + +/* Encode slave ID */ +static inline uint32_t fsi_smode_sid(int x) +{ + return (x & FSI_SMODE_SID_MASK) << FSI_SMODE_SID_SHIFT; +} + +static uint32_t fsi_slave_smode(int id) +{ + return FSI_SMODE_WSC | FSI_SMODE_ECRC + | fsi_smode_sid(id) + | fsi_smode_echodly(0xf) | fsi_smode_senddly(0xf) + | fsi_smode_lbcrr(0x8); +} + +static int fsi_slave_set_smode(struct fsi_master *master, int link, int id) +{ + uint32_t smode; + __be32 data; + + /* set our smode register with the slave ID field to 0; this enables + * extended slave addressing + */ + smode = fsi_slave_smode(id); + data = cpu_to_be32(smode); + + return fsi_master_write(master, link, id, FSI_SLAVE_BASE + FSI_SMODE, + &data, sizeof(data)); +} static int fsi_slave_handle_error(struct fsi_slave *slave, bool write, uint32_t addr, size_t size) @@ -563,53 +608,6 @@ static const struct bin_attribute fsi_slave_term_attr = { .write = fsi_slave_sysfs_term_write, }; -/* Encode slave local bus echo delay */ -static inline uint32_t fsi_smode_echodly(int x) -{ - return (x & FSI_SMODE_ED_MASK) << FSI_SMODE_ED_SHIFT; -} - -/* Encode slave local bus send delay */ -static inline uint32_t fsi_smode_senddly(int x) -{ - return (x & FSI_SMODE_SD_MASK) << FSI_SMODE_SD_SHIFT; -} - -/* Encode slave local bus clock rate ratio */ -static inline uint32_t fsi_smode_lbcrr(int x) -{ - return (x & FSI_SMODE_LBCRR_MASK) << FSI_SMODE_LBCRR_SHIFT; -} - -/* Encode slave ID */ -static inline uint32_t fsi_smode_sid(int x) -{ - return (x & FSI_SMODE_SID_MASK) << FSI_SMODE_SID_SHIFT; -} - -static uint32_t fsi_slave_smode(int id) -{ - return FSI_SMODE_WSC | FSI_SMODE_ECRC - | fsi_smode_sid(id) - | fsi_smode_echodly(0xf) | fsi_smode_senddly(0xf) - | fsi_smode_lbcrr(0x8); -} - -static int fsi_slave_set_smode(struct fsi_master *master, int link, int id) -{ - uint32_t smode; - __be32 data; - - /* set our smode register with the slave ID field to 0; this enables - * extended slave addressing - */ - smode = fsi_slave_smode(id); - data = cpu_to_be32(smode); - - return fsi_master_write(master, link, id, FSI_SLAVE_BASE + FSI_SMODE, - &data, sizeof(data)); -} - static void fsi_slave_release(struct device *dev) { struct fsi_slave *slave = to_fsi_slave(dev); -- cgit v1.2.3 From a2e7da86cc392417b0d9f605b28038aae80b002f Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Tue, 29 May 2018 15:01:07 +1000 Subject: fsi: Add mechanism to set the tSendDelay and tEchoDelay values Those values control the amount of "dummy" clocks between commands and between a command and its response. This adds a way to configure them from sysfs (to be later extended to defaults in the device-tree). The default remains 16 (the HW default). This is only supported if the backend supports the new link_config() callback to configure the generation of those delays. Signed-off-by: Benjamin Herrenschmidt Reviewed-by: Joel Stanley --- --- drivers/fsi/fsi-core.c | 109 +++++++++++++++++++++++++++++++++++++++-------- drivers/fsi/fsi-master.h | 2 + 2 files changed, 93 insertions(+), 18 deletions(-) diff --git a/drivers/fsi/fsi-core.c b/drivers/fsi/fsi-core.c index f039b348e484..372a9d8a8990 100644 --- a/drivers/fsi/fsi-core.c +++ b/drivers/fsi/fsi-core.c @@ -81,6 +81,8 @@ struct fsi_slave { int id; int link; uint32_t size; /* size of slave address space */ + u8 t_send_delay; + u8 t_echo_delay; }; #define to_fsi_master(d) container_of(d, struct fsi_master, dev) @@ -239,15 +241,15 @@ static inline uint32_t fsi_smode_sid(int x) return (x & FSI_SMODE_SID_MASK) << FSI_SMODE_SID_SHIFT; } -static uint32_t fsi_slave_smode(int id) +static uint32_t fsi_slave_smode(int id, u8 t_senddly, u8 t_echodly) { return FSI_SMODE_WSC | FSI_SMODE_ECRC | fsi_smode_sid(id) - | fsi_smode_echodly(0xf) | fsi_smode_senddly(0xf) + | fsi_smode_echodly(t_echodly - 1) | fsi_smode_senddly(t_senddly - 1) | fsi_smode_lbcrr(0x8); } -static int fsi_slave_set_smode(struct fsi_master *master, int link, int id) +static int fsi_slave_set_smode(struct fsi_slave *slave) { uint32_t smode; __be32 data; @@ -255,11 +257,12 @@ static int fsi_slave_set_smode(struct fsi_master *master, int link, int id) /* set our smode register with the slave ID field to 0; this enables * extended slave addressing */ - smode = fsi_slave_smode(id); + smode = fsi_slave_smode(slave->id, slave->t_send_delay, slave->t_echo_delay); data = cpu_to_be32(smode); - return fsi_master_write(master, link, id, FSI_SLAVE_BASE + FSI_SMODE, - &data, sizeof(data)); + return fsi_master_write(slave->master, slave->link, slave->id, + FSI_SLAVE_BASE + FSI_SMODE, + &data, sizeof(data)); } static int fsi_slave_handle_error(struct fsi_slave *slave, bool write, @@ -268,7 +271,7 @@ static int fsi_slave_handle_error(struct fsi_slave *slave, bool write, struct fsi_master *master = slave->master; int rc, link; uint32_t reg; - uint8_t id; + uint8_t id, send_delay, echo_delay; if (discard_errors) return -1; @@ -299,15 +302,26 @@ static int fsi_slave_handle_error(struct fsi_slave *slave, bool write, } } + send_delay = slave->t_send_delay; + echo_delay = slave->t_echo_delay; + /* getting serious, reset the slave via BREAK */ rc = fsi_master_break(master, link); if (rc) return rc; - rc = fsi_slave_set_smode(master, link, id); + slave->t_send_delay = send_delay; + slave->t_echo_delay = echo_delay; + + rc = fsi_slave_set_smode(slave); if (rc) return rc; + if (master->link_config) + master->link_config(master, link, + slave->t_send_delay, + slave->t_echo_delay); + return fsi_slave_report_and_clear_errors(slave); } @@ -659,6 +673,50 @@ static struct device_node *fsi_slave_find_of_node(struct fsi_master *master, return NULL; } +static ssize_t slave_send_echo_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct fsi_slave *slave = to_fsi_slave(dev); + + return sprintf(buf, "%u\n", slave->t_send_delay); +} + +static ssize_t slave_send_echo_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct fsi_slave *slave = to_fsi_slave(dev); + struct fsi_master *master = slave->master; + unsigned long val; + int rc; + + if (kstrtoul(buf, 0, &val) < 0) + return -EINVAL; + + if (val < 1 || val > 16) + return -EINVAL; + + if (!master->link_config) + return -ENXIO; + + /* Current HW mandates that send and echo delay are identical */ + slave->t_send_delay = val; + slave->t_echo_delay = val; + + rc = fsi_slave_set_smode(slave); + if (rc < 0) + return rc; + if (master->link_config) + master->link_config(master, slave->link, + slave->t_send_delay, + slave->t_echo_delay); + + return count; +} + +static DEVICE_ATTR(send_echo_delays, 0600, + slave_send_echo_show, slave_send_echo_store); + static int fsi_slave_init(struct fsi_master *master, int link, uint8_t id) { uint32_t chip_id; @@ -691,14 +749,6 @@ static int fsi_slave_init(struct fsi_master *master, int link, uint8_t id) dev_dbg(&master->dev, "fsi: found chip %08x at %02x:%02x:%02x\n", chip_id, master->idx, link, id); - rc = fsi_slave_set_smode(master, link, id); - if (rc) { - dev_warn(&master->dev, - "can't set smode on slave:%02x:%02x %d\n", - link, id, rc); - return -ENODEV; - } - /* If we're behind a master that doesn't provide a self-running bus * clock, put the slave into async mode */ @@ -727,6 +777,21 @@ static int fsi_slave_init(struct fsi_master *master, int link, uint8_t id) slave->link = link; slave->id = id; slave->size = FSI_SLAVE_SIZE_23b; + slave->t_send_delay = 16; + slave->t_echo_delay = 16; + + rc = fsi_slave_set_smode(slave); + if (rc) { + dev_warn(&master->dev, + "can't set smode on slave:%02x:%02x %d\n", + link, id, rc); + kfree(slave); + return -ENODEV; + } + if (master->link_config) + master->link_config(master, link, + slave->t_send_delay, + slave->t_echo_delay); dev_set_name(&slave->dev, "slave@%02x:%02x", link, id); rc = device_register(&slave->dev); @@ -745,6 +810,10 @@ static int fsi_slave_init(struct fsi_master *master, int link, uint8_t id) if (rc) dev_warn(&slave->dev, "failed to create term attr: %d\n", rc); + rc = device_create_file(&slave->dev, &dev_attr_send_echo_delays); + if (rc) + dev_warn(&slave->dev, "failed to create delay attr: %d\n", rc); + rc = fsi_slave_scan(slave); if (rc) dev_dbg(&master->dev, "failed during slave scan with: %d\n", @@ -815,12 +884,16 @@ static int fsi_master_link_enable(struct fsi_master *master, int link) */ static int fsi_master_break(struct fsi_master *master, int link) { + int rc = 0; + trace_fsi_master_break(master, link); if (master->send_break) - return master->send_break(master, link); + rc = master->send_break(master, link); + if (master->link_config) + master->link_config(master, link, 16, 16); - return 0; + return rc; } static int fsi_master_scan(struct fsi_master *master) diff --git a/drivers/fsi/fsi-master.h b/drivers/fsi/fsi-master.h index ee0b46086026..7d619c68ab9b 100644 --- a/drivers/fsi/fsi-master.h +++ b/drivers/fsi/fsi-master.h @@ -33,6 +33,8 @@ struct fsi_master { int (*term)(struct fsi_master *, int link, uint8_t id); int (*send_break)(struct fsi_master *, int link); int (*link_enable)(struct fsi_master *, int link); + int (*link_config)(struct fsi_master *, int link, + u8 t_send_delay, u8 t_echo_delay); }; #define dev_to_fsi_master(d) container_of(d, struct fsi_master, dev) -- cgit v1.2.3 From edc2485148d20c56c67d9ceec4d645c18d795a8d Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Tue, 29 May 2018 15:09:45 +1000 Subject: fsi: master-gpio: Rename and adjust send delay What the driver called "FSI_GPIO_PRIME_SLAVE_CLOCKS" is what the FSI spec calls tSendDelay and should be 16 clocks by default. Signed-off-by: Benjamin Herrenschmidt Reviewed-by: Joel Stanley --- drivers/fsi/fsi-master-gpio.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/fsi/fsi-master-gpio.c b/drivers/fsi/fsi-master-gpio.c index 084e9da8d151..3f846dada626 100644 --- a/drivers/fsi/fsi-master-gpio.c +++ b/drivers/fsi/fsi-master-gpio.c @@ -18,6 +18,7 @@ #define FSI_GPIO_STD_DLY 1 /* Standard pin delay in nS */ #define FSI_ECHO_DELAY_CLOCKS 16 /* Number clocks for echo delay */ +#define FSI_SEND_DELAY_CLOCKS 16 /* Number clocks for send delay */ #define FSI_PRE_BREAK_CLOCKS 50 /* Number clocks to prep for break */ #define FSI_BREAK_CLOCKS 256 /* Number of clocks to issue break */ #define FSI_POST_BREAK_CLOCKS 16000 /* Number clocks to set up cfam */ @@ -48,7 +49,6 @@ #define FSI_GPIO_CRC_SIZE 4 #define FSI_GPIO_MSG_ID_SIZE 2 #define FSI_GPIO_MSG_RESPID_SIZE 2 -#define FSI_GPIO_PRIME_SLAVE_CLOCKS 20 #define LAST_ADDR_INVALID 0x1 @@ -535,9 +535,12 @@ retry: if (busy_count > 0) trace_fsi_master_gpio_poll_response_busy(master, busy_count); fail: - /* Clock the slave enough to be ready for next operation */ + /* + * tSendDelay clocks, avoids signal reflections when switching + * from receive of response back to send of data. + */ local_irq_save(flags); - clock_zeros(master, FSI_GPIO_PRIME_SLAVE_CLOCKS); + clock_zeros(master, FSI_SEND_DELAY_CLOCKS); local_irq_restore(flags); return rc; -- cgit v1.2.3 From 75854c148fa5983ff02f630a0eb2d80998fe12ae Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Tue, 29 May 2018 15:11:32 +1000 Subject: fsi: master-gpio: Add support for link_config To configure the send and echo delays Signed-off-by: Benjamin Herrenschmidt Reviewed-by: Joel Stanley --- drivers/fsi/fsi-master-gpio.c | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/drivers/fsi/fsi-master-gpio.c b/drivers/fsi/fsi-master-gpio.c index 3f846dada626..a8445ec21b4a 100644 --- a/drivers/fsi/fsi-master-gpio.c +++ b/drivers/fsi/fsi-master-gpio.c @@ -64,6 +64,8 @@ struct fsi_master_gpio { bool external_mode; bool no_delays; uint32_t last_addr; + uint8_t t_send_delay; + uint8_t t_echo_delay; }; #define CREATE_TRACE_POINTS @@ -338,7 +340,7 @@ static void build_epoll_command(struct fsi_gpio_msg *cmd, uint8_t slave_id) static void echo_delay(struct fsi_master_gpio *master) { set_sda_output(master, 1); - clock_toggle(master, FSI_ECHO_DELAY_CLOCKS); + clock_toggle(master, master->t_echo_delay); } static void build_term_command(struct fsi_gpio_msg *cmd, uint8_t slave_id) @@ -540,7 +542,7 @@ retry: * from receive of response back to send of data. */ local_irq_save(flags); - clock_zeros(master, FSI_SEND_DELAY_CLOCKS); + clock_zeros(master, master->t_send_delay); local_irq_restore(flags); return rc; @@ -722,6 +724,22 @@ static int fsi_master_gpio_link_enable(struct fsi_master *_master, int link) return rc; } +static int fsi_master_gpio_link_config(struct fsi_master *_master, int link, + u8 t_send_delay, u8 t_echo_delay) +{ + struct fsi_master_gpio *master = to_fsi_master_gpio(_master); + + if (link != 0) + return -ENODEV; + + mutex_lock(&master->cmd_lock); + master->t_send_delay = t_send_delay; + master->t_echo_delay = t_echo_delay; + mutex_unlock(&master->cmd_lock); + + return 0; +} + static ssize_t external_mode_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -826,6 +844,10 @@ static int fsi_master_gpio_probe(struct platform_device *pdev) */ master->no_delays = device_property_present(&pdev->dev, "no-gpio-delays"); + /* Default FSI command delays */ + master->t_send_delay = FSI_SEND_DELAY_CLOCKS; + master->t_echo_delay = FSI_ECHO_DELAY_CLOCKS; + master->master.n_links = 1; master->master.flags = FSI_MASTER_FLAG_SWCLOCK; master->master.read = fsi_master_gpio_read; @@ -833,6 +855,7 @@ static int fsi_master_gpio_probe(struct platform_device *pdev) master->master.term = fsi_master_gpio_term; master->master.send_break = fsi_master_gpio_break; master->master.link_enable = fsi_master_gpio_link_enable; + master->master.link_config = fsi_master_gpio_link_config; platform_set_drvdata(pdev, master); mutex_init(&master->cmd_lock); -- cgit v1.2.3 From 777fd524ba197475a60198aa1666408662acbcbc Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Tue, 29 May 2018 19:28:38 +1000 Subject: fsi: master-gpio: Add more tracepoints This adds a few more tracepoints that have proven useful when debugging issues with the FSI bus. This also makes echo_delay() use clock_zeros() instead of open-code it in order to share the tracepoint. Signed-off-by: Benjamin Herrenschmidt Reviewed-by: Joel Stanley --- drivers/fsi/fsi-master-gpio.c | 16 +++++---- include/trace/events/fsi_master_gpio.h | 59 ++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 6 deletions(-) diff --git a/drivers/fsi/fsi-master-gpio.c b/drivers/fsi/fsi-master-gpio.c index a8445ec21b4a..40cbaf96547c 100644 --- a/drivers/fsi/fsi-master-gpio.c +++ b/drivers/fsi/fsi-master-gpio.c @@ -130,10 +130,17 @@ static void set_sda_output(struct fsi_master_gpio *master, int value) static void clock_zeros(struct fsi_master_gpio *master, int count) { + trace_fsi_master_gpio_clock_zeros(master, count); set_sda_output(master, 1); clock_toggle(master, count); } +static void echo_delay(struct fsi_master_gpio *master) +{ + clock_zeros(master, master->t_echo_delay); +} + + static void serial_in(struct fsi_master_gpio *master, struct fsi_gpio_msg *msg, uint8_t num_bits) { @@ -279,16 +286,19 @@ static void build_ar_command(struct fsi_master_gpio *master, addr_bits = 2; opcode_bits = 2; opcode = FSI_GPIO_CMD_SAME_AR; + trace_fsi_master_gpio_cmd_same_addr(master); } else if (check_relative_address(master, id, addr, &rel_addr)) { /* 8 bits plus sign */ addr_bits = 9; addr = rel_addr; opcode = FSI_GPIO_CMD_REL_AR; + trace_fsi_master_gpio_cmd_rel_addr(master, rel_addr); } else { addr_bits = 21; opcode = FSI_GPIO_CMD_ABS_AR; + trace_fsi_master_gpio_cmd_abs_addr(master, addr); } /* @@ -337,12 +347,6 @@ static void build_epoll_command(struct fsi_gpio_msg *cmd, uint8_t slave_id) msg_push_crc(cmd); } -static void echo_delay(struct fsi_master_gpio *master) -{ - set_sda_output(master, 1); - clock_toggle(master, master->t_echo_delay); -} - static void build_term_command(struct fsi_gpio_msg *cmd, uint8_t slave_id) { cmd->bits = 0; diff --git a/include/trace/events/fsi_master_gpio.h b/include/trace/events/fsi_master_gpio.h index 389082132433..70ef66e63e84 100644 --- a/include/trace/events/fsi_master_gpio.h +++ b/include/trace/events/fsi_master_gpio.h @@ -50,6 +50,22 @@ TRACE_EVENT(fsi_master_gpio_out, ) ); +TRACE_EVENT(fsi_master_gpio_clock_zeros, + TP_PROTO(const struct fsi_master_gpio *master, int clocks), + TP_ARGS(master, clocks), + TP_STRUCT__entry( + __field(int, master_idx) + __field(int, clocks) + ), + TP_fast_assign( + __entry->master_idx = master->master.idx; + __entry->clocks = clocks; + ), + TP_printk("fsi-gpio%d clock %d zeros", + __entry->master_idx, __entry->clocks + ) +); + TRACE_EVENT(fsi_master_gpio_break, TP_PROTO(const struct fsi_master_gpio *master), TP_ARGS(master), @@ -107,6 +123,49 @@ TRACE_EVENT(fsi_master_gpio_poll_response_busy, __entry->master_idx, __entry->busy) ); +TRACE_EVENT(fsi_master_gpio_cmd_abs_addr, + TP_PROTO(const struct fsi_master_gpio *master, u32 addr), + TP_ARGS(master, addr), + TP_STRUCT__entry( + __field(int, master_idx) + __field(u32, addr) + ), + TP_fast_assign( + __entry->master_idx = master->master.idx; + __entry->addr = addr; + ), + TP_printk("fsi-gpio%d: Sending ABS_ADR %06x", + __entry->master_idx, __entry->addr) +); + +TRACE_EVENT(fsi_master_gpio_cmd_rel_addr, + TP_PROTO(const struct fsi_master_gpio *master, u32 rel_addr), + TP_ARGS(master, rel_addr), + TP_STRUCT__entry( + __field(int, master_idx) + __field(u32, rel_addr) + ), + TP_fast_assign( + __entry->master_idx = master->master.idx; + __entry->rel_addr = rel_addr; + ), + TP_printk("fsi-gpio%d: Sending REL_ADR %03x", + __entry->master_idx, __entry->rel_addr) +); + +TRACE_EVENT(fsi_master_gpio_cmd_same_addr, + TP_PROTO(const struct fsi_master_gpio *master), + TP_ARGS(master), + TP_STRUCT__entry( + __field(int, master_idx) + ), + TP_fast_assign( + __entry->master_idx = master->master.idx; + ), + TP_printk("fsi-gpio%d: Sending SAME_ADR", + __entry->master_idx) +); + #endif /* _TRACE_FSI_MASTER_GPIO_H */ #include -- cgit v1.2.3 From 8b2e4751131396d942e2b99682623ffb750dada5 Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Sun, 10 Jun 2018 16:20:18 +1000 Subject: fsi: master-gpio: Remove unused definitions Signed-off-by: Benjamin Herrenschmidt Reviewed-by: Joel Stanley --- drivers/fsi/fsi-master-gpio.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/drivers/fsi/fsi-master-gpio.c b/drivers/fsi/fsi-master-gpio.c index 40cbaf96547c..b819b3f943ef 100644 --- a/drivers/fsi/fsi-master-gpio.c +++ b/drivers/fsi/fsi-master-gpio.c @@ -25,9 +25,6 @@ #define FSI_INIT_CLOCKS 5000 /* Clock out any old data */ #define FSI_GPIO_DPOLL_CLOCKS 50 /* < 21 will cause slave to hang */ #define FSI_GPIO_EPOLL_CLOCKS 50 /* Number of clocks for E_POLL retry */ -#define FSI_GPIO_STD_DELAY 10 /* Standard GPIO delay in nS */ - /* todo: adjust down as low as */ - /* possible or eliminate */ #define FSI_CRC_ERR_RETRIES 10 #define FSI_GPIO_CMD_DPOLL 0x2 @@ -45,10 +42,7 @@ #define FSI_GPIO_MAX_BUSY 200 #define FSI_GPIO_MTOE_COUNT 1000 -#define FSI_GPIO_DRAIN_BITS 20 #define FSI_GPIO_CRC_SIZE 4 -#define FSI_GPIO_MSG_ID_SIZE 2 -#define FSI_GPIO_MSG_RESPID_SIZE 2 #define LAST_ADDR_INVALID 0x1 -- cgit v1.2.3 From 55382d301fd8992349da0e58c013400e2e72417d Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Thu, 12 Jul 2018 12:04:24 +1000 Subject: fsi: master-gpio: Remove "GPIO" prefix on some definitions Some definitions are generic to the FSI protocol or any give master implementation. Rename them to remove the "GPIO" prefix in preparation for moving them to a common header. Signed-off-by: Benjamin Herrenschmidt Reviewed-by: Joel Stanley # Conflicts: # drivers/fsi/fsi-master-gpio.c --- drivers/fsi/fsi-master-gpio.c | 70 ++++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 34 deletions(-) diff --git a/drivers/fsi/fsi-master-gpio.c b/drivers/fsi/fsi-master-gpio.c index b819b3f943ef..a589a44bd805 100644 --- a/drivers/fsi/fsi-master-gpio.c +++ b/drivers/fsi/fsi-master-gpio.c @@ -23,26 +23,28 @@ #define FSI_BREAK_CLOCKS 256 /* Number of clocks to issue break */ #define FSI_POST_BREAK_CLOCKS 16000 /* Number clocks to set up cfam */ #define FSI_INIT_CLOCKS 5000 /* Clock out any old data */ -#define FSI_GPIO_DPOLL_CLOCKS 50 /* < 21 will cause slave to hang */ -#define FSI_GPIO_EPOLL_CLOCKS 50 /* Number of clocks for E_POLL retry */ +#define FSI_MASTER_DPOLL_CLOCKS 50 /* < 21 will cause slave to hang */ +#define FSI_MASTER_EPOLL_CLOCKS 50 /* Number of clocks for E_POLL retry */ + #define FSI_CRC_ERR_RETRIES 10 -#define FSI_GPIO_CMD_DPOLL 0x2 -#define FSI_GPIO_CMD_EPOLL 0x3 -#define FSI_GPIO_CMD_TERM 0x3f -#define FSI_GPIO_CMD_ABS_AR 0x4 -#define FSI_GPIO_CMD_REL_AR 0x5 -#define FSI_GPIO_CMD_SAME_AR 0x3 /* but only a 2-bit opcode... */ +#define FSI_CMD_DPOLL 0x2 +#define FSI_CMD_EPOLL 0x3 +#define FSI_CMD_TERM 0x3f +#define FSI_CMD_ABS_AR 0x4 +#define FSI_CMD_REL_AR 0x5 +#define FSI_CMD_SAME_AR 0x3 /* but only a 2-bit opcode... */ /* Slave responses */ -#define FSI_GPIO_RESP_ACK 0 /* Success */ -#define FSI_GPIO_RESP_BUSY 1 /* Slave busy */ -#define FSI_GPIO_RESP_ERRA 2 /* Any (misc) Error */ -#define FSI_GPIO_RESP_ERRC 3 /* Slave reports master CRC error */ +#define FSI_RESP_ACK 0 /* Success */ +#define FSI_RESP_BUSY 1 /* Slave busy */ +#define FSI_RESP_ERRA 2 /* Any (misc) Error */ +#define FSI_RESP_ERRC 3 /* Slave reports master CRC error */ + +#define FSI_MASTER_MAX_BUSY 200 -#define FSI_GPIO_MAX_BUSY 200 -#define FSI_GPIO_MTOE_COUNT 1000 -#define FSI_GPIO_CRC_SIZE 4 +#define FSI_MASTER_MTOE_COUNT 1000 +#define FSI_CRC_SIZE 4 #define LAST_ADDR_INVALID 0x1 @@ -279,19 +281,19 @@ static void build_ar_command(struct fsi_master_gpio *master, /* we still address the byte offset within the word */ addr_bits = 2; opcode_bits = 2; - opcode = FSI_GPIO_CMD_SAME_AR; + opcode = FSI_CMD_SAME_AR; trace_fsi_master_gpio_cmd_same_addr(master); } else if (check_relative_address(master, id, addr, &rel_addr)) { /* 8 bits plus sign */ addr_bits = 9; addr = rel_addr; - opcode = FSI_GPIO_CMD_REL_AR; + opcode = FSI_CMD_REL_AR; trace_fsi_master_gpio_cmd_rel_addr(master, rel_addr); } else { addr_bits = 21; - opcode = FSI_GPIO_CMD_ABS_AR; + opcode = FSI_CMD_ABS_AR; trace_fsi_master_gpio_cmd_abs_addr(master, addr); } @@ -327,7 +329,7 @@ static void build_dpoll_command(struct fsi_gpio_msg *cmd, uint8_t slave_id) cmd->msg = 0; msg_push_bits(cmd, slave_id, 2); - msg_push_bits(cmd, FSI_GPIO_CMD_DPOLL, 3); + msg_push_bits(cmd, FSI_CMD_DPOLL, 3); msg_push_crc(cmd); } @@ -337,7 +339,7 @@ static void build_epoll_command(struct fsi_gpio_msg *cmd, uint8_t slave_id) cmd->msg = 0; msg_push_bits(cmd, slave_id, 2); - msg_push_bits(cmd, FSI_GPIO_CMD_EPOLL, 3); + msg_push_bits(cmd, FSI_CMD_EPOLL, 3); msg_push_crc(cmd); } @@ -347,7 +349,7 @@ static void build_term_command(struct fsi_gpio_msg *cmd, uint8_t slave_id) cmd->msg = 0; msg_push_bits(cmd, slave_id, 2); - msg_push_bits(cmd, FSI_GPIO_CMD_TERM, 6); + msg_push_bits(cmd, FSI_CMD_TERM, 6); msg_push_crc(cmd); } @@ -369,14 +371,14 @@ static int read_one_response(struct fsi_master_gpio *master, local_irq_save(flags); /* wait for the start bit */ - for (i = 0; i < FSI_GPIO_MTOE_COUNT; i++) { + for (i = 0; i < FSI_MASTER_MTOE_COUNT; i++) { msg.bits = 0; msg.msg = 0; serial_in(master, &msg, 1); if (msg.msg) break; } - if (i == FSI_GPIO_MTOE_COUNT) { + if (i == FSI_MASTER_MTOE_COUNT) { dev_dbg(master->dev, "Master time out waiting for response\n"); local_irq_restore(flags); @@ -392,11 +394,11 @@ static int read_one_response(struct fsi_master_gpio *master, tag = msg.msg & 0x3; /* If we have an ACK and we're expecting data, clock the data in too */ - if (tag == FSI_GPIO_RESP_ACK && data_size) + if (tag == FSI_RESP_ACK && data_size) serial_in(master, &msg, data_size * 8); /* read CRC */ - serial_in(master, &msg, FSI_GPIO_CRC_SIZE); + serial_in(master, &msg, FSI_CRC_SIZE); local_irq_restore(flags); @@ -439,7 +441,7 @@ static int issue_term(struct fsi_master_gpio *master, uint8_t slave) dev_err(master->dev, "TERM failed; lost communication with slave\n"); return -EIO; - } else if (tag != FSI_GPIO_RESP_ACK) { + } else if (tag != FSI_RESP_ACK) { dev_err(master->dev, "TERM failed; response %d\n", tag); return -EIO; } @@ -475,7 +477,7 @@ retry: trace_fsi_master_gpio_crc_rsp_error(master); build_epoll_command(&cmd, slave); local_irq_save(flags); - clock_zeros(master, FSI_GPIO_EPOLL_CLOCKS); + clock_zeros(master, FSI_MASTER_EPOLL_CLOCKS); serial_out(master, &cmd); echo_delay(master); local_irq_restore(flags); @@ -484,7 +486,7 @@ retry: goto fail; switch (tag) { - case FSI_GPIO_RESP_ACK: + case FSI_RESP_ACK: if (size && data) { uint64_t val = response.msg; /* clear crc & mask */ @@ -497,16 +499,16 @@ retry: } } break; - case FSI_GPIO_RESP_BUSY: + case FSI_RESP_BUSY: /* * Its necessary to clock slave before issuing * d-poll, not indicated in the hardware protocol * spec. < 20 clocks causes slave to hang, 21 ok. */ - if (busy_count++ < FSI_GPIO_MAX_BUSY) { + if (busy_count++ < FSI_MASTER_MAX_BUSY) { build_dpoll_command(&cmd, slave); local_irq_save(flags); - clock_zeros(master, FSI_GPIO_DPOLL_CLOCKS); + clock_zeros(master, FSI_MASTER_DPOLL_CLOCKS); serial_out(master, &cmd); echo_delay(master); local_irq_restore(flags); @@ -515,17 +517,17 @@ retry: dev_warn(master->dev, "ERR slave is stuck in busy state, issuing TERM\n"); local_irq_save(flags); - clock_zeros(master, FSI_GPIO_DPOLL_CLOCKS); + clock_zeros(master, FSI_MASTER_DPOLL_CLOCKS); local_irq_restore(flags); issue_term(master, slave); rc = -EIO; break; - case FSI_GPIO_RESP_ERRA: + case FSI_RESP_ERRA: dev_dbg(master->dev, "ERRA received: 0x%x\n", (int)response.msg); rc = -EIO; break; - case FSI_GPIO_RESP_ERRC: + case FSI_RESP_ERRC: dev_dbg(master->dev, "ERRC received: 0x%x\n", (int)response.msg); trace_fsi_master_gpio_crc_cmd_error(master); rc = -EAGAIN; -- cgit v1.2.3 From 265aac26bcd4a2a79d08d2bdcc08cf1653fd4248 Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Thu, 28 Jun 2018 16:26:19 +1000 Subject: fsi: Don't use device_unregister() in fsi_master_register() In the error path of fsi_master_register(), we currently use device_unregister(). This will cause the last reference to the structure to be dropped, thus freeing the enclosing structure, which isn't what the callers want. Use device_del() instead so that we return to the caller with a refcount of 1. The caller can then assume that it must use put_device() after a call to fsi_master_register() regardless of whether the latter suceeded or failed. Signed-off-by: Benjamin Herrenschmidt --- drivers/fsi/fsi-core.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/fsi/fsi-core.c b/drivers/fsi/fsi-core.c index 372a9d8a8990..e9f8813b75e6 100644 --- a/drivers/fsi/fsi-core.c +++ b/drivers/fsi/fsi-core.c @@ -977,9 +977,6 @@ int fsi_master_register(struct fsi_master *master) int rc; struct device_node *np; - if (!master) - return -EINVAL; - master->idx = ida_simple_get(&master_ida, 0, INT_MAX, GFP_KERNEL); dev_set_name(&master->dev, "fsi%d", master->idx); @@ -991,14 +988,14 @@ int fsi_master_register(struct fsi_master *master) rc = device_create_file(&master->dev, &dev_attr_rescan); if (rc) { - device_unregister(&master->dev); + device_del(&master->dev); ida_simple_remove(&master_ida, master->idx); return rc; } rc = device_create_file(&master->dev, &dev_attr_break); if (rc) { - device_unregister(&master->dev); + device_del(&master->dev); ida_simple_remove(&master_ida, master->idx); return rc; } -- cgit v1.2.3 From 8ef9ccf81044a1e31d116b5d4972d5b65ae10846 Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Wed, 13 Jun 2018 10:05:17 +1000 Subject: fsi: master-gpio: Add missing release function The embedded struct device needs a release function to be able to successfully remove the driver. We remove the devm_gpiod_put() as they are unnecessary (the resources will be released automatically) and because fsi_master_unregister() will cause the master structure to be freed. Signed-off-by: Benjamin Herrenschmidt Reviewed-by: Joel Stanley --- drivers/fsi/fsi-master-gpio.c | 53 ++++++++++++++++++++++++++++--------------- 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/drivers/fsi/fsi-master-gpio.c b/drivers/fsi/fsi-master-gpio.c index a589a44bd805..c4a06210ddce 100644 --- a/drivers/fsi/fsi-master-gpio.c +++ b/drivers/fsi/fsi-master-gpio.c @@ -786,32 +786,44 @@ static ssize_t external_mode_store(struct device *dev, static DEVICE_ATTR(external_mode, 0664, external_mode_show, external_mode_store); +static void fsi_master_gpio_release(struct device *dev) +{ + struct fsi_master_gpio *master = to_fsi_master_gpio(dev_to_fsi_master(dev)); + + of_node_put(dev_of_node(master->dev)); + + kfree(master); +} + static int fsi_master_gpio_probe(struct platform_device *pdev) { struct fsi_master_gpio *master; struct gpio_desc *gpio; int rc; - master = devm_kzalloc(&pdev->dev, sizeof(*master), GFP_KERNEL); + master = kzalloc(sizeof(*master), GFP_KERNEL); if (!master) return -ENOMEM; master->dev = &pdev->dev; master->master.dev.parent = master->dev; master->master.dev.of_node = of_node_get(dev_of_node(master->dev)); + master->master.dev.release = fsi_master_gpio_release; master->last_addr = LAST_ADDR_INVALID; gpio = devm_gpiod_get(&pdev->dev, "clock", 0); if (IS_ERR(gpio)) { dev_err(&pdev->dev, "failed to get clock gpio\n"); - return PTR_ERR(gpio); + rc = PTR_ERR(gpio); + goto err_free; } master->gpio_clk = gpio; gpio = devm_gpiod_get(&pdev->dev, "data", 0); if (IS_ERR(gpio)) { dev_err(&pdev->dev, "failed to get data gpio\n"); - return PTR_ERR(gpio); + rc = PTR_ERR(gpio); + goto err_free; } master->gpio_data = gpio; @@ -819,21 +831,24 @@ static int fsi_master_gpio_probe(struct platform_device *pdev) gpio = devm_gpiod_get_optional(&pdev->dev, "trans", 0); if (IS_ERR(gpio)) { dev_err(&pdev->dev, "failed to get trans gpio\n"); - return PTR_ERR(gpio); + rc = PTR_ERR(gpio); + goto err_free; } master->gpio_trans = gpio; gpio = devm_gpiod_get_optional(&pdev->dev, "enable", 0); if (IS_ERR(gpio)) { dev_err(&pdev->dev, "failed to get enable gpio\n"); - return PTR_ERR(gpio); + rc = PTR_ERR(gpio); + goto err_free; } master->gpio_enable = gpio; gpio = devm_gpiod_get_optional(&pdev->dev, "mux", 0); if (IS_ERR(gpio)) { dev_err(&pdev->dev, "failed to get mux gpio\n"); - return PTR_ERR(gpio); + rc = PTR_ERR(gpio); + goto err_free; } master->gpio_mux = gpio; @@ -863,27 +878,29 @@ static int fsi_master_gpio_probe(struct platform_device *pdev) rc = device_create_file(&pdev->dev, &dev_attr_external_mode); if (rc) - return rc; + goto err_free; - return fsi_master_register(&master->master); + rc = fsi_master_register(&master->master); + if (rc) { + device_remove_file(&pdev->dev, &dev_attr_external_mode); + put_device(&master->master.dev); + return rc; + } + return 0; + err_free: + kfree(master); + return rc; } + static int fsi_master_gpio_remove(struct platform_device *pdev) { struct fsi_master_gpio *master = platform_get_drvdata(pdev); - devm_gpiod_put(&pdev->dev, master->gpio_clk); - devm_gpiod_put(&pdev->dev, master->gpio_data); - if (master->gpio_trans) - devm_gpiod_put(&pdev->dev, master->gpio_trans); - if (master->gpio_enable) - devm_gpiod_put(&pdev->dev, master->gpio_enable); - if (master->gpio_mux) - devm_gpiod_put(&pdev->dev, master->gpio_mux); - fsi_master_unregister(&master->master); + device_remove_file(&pdev->dev, &dev_attr_external_mode); - of_node_put(master->master.dev.of_node); + fsi_master_unregister(&master->master); return 0; } -- cgit v1.2.3 From fea9cf321c916e9372874e6f2af1bf0b5beb89fb Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Sun, 10 Jun 2018 16:25:25 +1000 Subject: fsi: Move various master definitions to a common header This moves the definitions for various protocol details (message & response codes, delays etc...) out of fsi-master-gpio.c to fsi-master.h in order to share them with other master implementations. Signed-off-by: Benjamin Herrenschmidt Reviewed-by: Joel Stanley --- drivers/fsi/fsi-master-gpio.c | 29 ----------------------------- drivers/fsi/fsi-master.h | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 29 deletions(-) diff --git a/drivers/fsi/fsi-master-gpio.c b/drivers/fsi/fsi-master-gpio.c index c4a06210ddce..4eb3a766fd4a 100644 --- a/drivers/fsi/fsi-master-gpio.c +++ b/drivers/fsi/fsi-master-gpio.c @@ -17,35 +17,6 @@ #include "fsi-master.h" #define FSI_GPIO_STD_DLY 1 /* Standard pin delay in nS */ -#define FSI_ECHO_DELAY_CLOCKS 16 /* Number clocks for echo delay */ -#define FSI_SEND_DELAY_CLOCKS 16 /* Number clocks for send delay */ -#define FSI_PRE_BREAK_CLOCKS 50 /* Number clocks to prep for break */ -#define FSI_BREAK_CLOCKS 256 /* Number of clocks to issue break */ -#define FSI_POST_BREAK_CLOCKS 16000 /* Number clocks to set up cfam */ -#define FSI_INIT_CLOCKS 5000 /* Clock out any old data */ -#define FSI_MASTER_DPOLL_CLOCKS 50 /* < 21 will cause slave to hang */ -#define FSI_MASTER_EPOLL_CLOCKS 50 /* Number of clocks for E_POLL retry */ - -#define FSI_CRC_ERR_RETRIES 10 - -#define FSI_CMD_DPOLL 0x2 -#define FSI_CMD_EPOLL 0x3 -#define FSI_CMD_TERM 0x3f -#define FSI_CMD_ABS_AR 0x4 -#define FSI_CMD_REL_AR 0x5 -#define FSI_CMD_SAME_AR 0x3 /* but only a 2-bit opcode... */ - -/* Slave responses */ -#define FSI_RESP_ACK 0 /* Success */ -#define FSI_RESP_BUSY 1 /* Slave busy */ -#define FSI_RESP_ERRA 2 /* Any (misc) Error */ -#define FSI_RESP_ERRC 3 /* Slave reports master CRC error */ - -#define FSI_MASTER_MAX_BUSY 200 - -#define FSI_MASTER_MTOE_COUNT 1000 -#define FSI_CRC_SIZE 4 - #define LAST_ADDR_INVALID 0x1 struct fsi_master_gpio { diff --git a/drivers/fsi/fsi-master.h b/drivers/fsi/fsi-master.h index 7d619c68ab9b..f653f75da7be 100644 --- a/drivers/fsi/fsi-master.h +++ b/drivers/fsi/fsi-master.h @@ -19,6 +19,39 @@ #include +/* Various protocol delays */ +#define FSI_ECHO_DELAY_CLOCKS 16 /* Number clocks for echo delay */ +#define FSI_SEND_DELAY_CLOCKS 16 /* Number clocks for send delay */ +#define FSI_PRE_BREAK_CLOCKS 50 /* Number clocks to prep for break */ +#define FSI_BREAK_CLOCKS 256 /* Number of clocks to issue break */ +#define FSI_POST_BREAK_CLOCKS 16000 /* Number clocks to set up cfam */ +#define FSI_INIT_CLOCKS 5000 /* Clock out any old data */ +#define FSI_MASTER_DPOLL_CLOCKS 50 /* < 21 will cause slave to hang */ +#define FSI_MASTER_EPOLL_CLOCKS 50 /* Number of clocks for E_POLL retry */ + +/* Various retry maximums */ +#define FSI_CRC_ERR_RETRIES 10 +#define FSI_MASTER_MAX_BUSY 200 +#define FSI_MASTER_MTOE_COUNT 1000 + +/* Command encodings */ +#define FSI_CMD_DPOLL 0x2 +#define FSI_CMD_EPOLL 0x3 +#define FSI_CMD_TERM 0x3f +#define FSI_CMD_ABS_AR 0x4 +#define FSI_CMD_REL_AR 0x5 +#define FSI_CMD_SAME_AR 0x3 /* but only a 2-bit opcode... */ + +/* Slave responses */ +#define FSI_RESP_ACK 0 /* Success */ +#define FSI_RESP_BUSY 1 /* Slave busy */ +#define FSI_RESP_ERRA 2 /* Any (misc) Error */ +#define FSI_RESP_ERRC 3 /* Slave reports master CRC error */ + +/* Misc */ +#define FSI_CRC_SIZE 4 + +/* fsi-master definition and flags */ #define FSI_MASTER_FLAG_SWCLOCK 0x1 struct fsi_master { -- cgit v1.2.3 From b40b3e9358fbafff6a4ba0f4b9658f6617146f9c Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Wed, 11 Jul 2018 15:29:31 +0300 Subject: mei: bus: type promotion bug in mei_nfc_if_version() We accidentally removed the check for negative returns without considering the issue of type promotion. The "if_version_length" variable is type size_t so if __mei_cl_recv() returns a negative then "bytes_recv" is type promoted to a high positive value and treated as success. Cc: Fixes: 582ab27a063a ("mei: bus: fix received data size check in NFC fixup") Signed-off-by: Dan Carpenter Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/bus-fixup.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/misc/mei/bus-fixup.c b/drivers/misc/mei/bus-fixup.c index e45fe826d87d..65e28be3c8cc 100644 --- a/drivers/misc/mei/bus-fixup.c +++ b/drivers/misc/mei/bus-fixup.c @@ -341,7 +341,7 @@ static int mei_nfc_if_version(struct mei_cl *cl, ret = 0; bytes_recv = __mei_cl_recv(cl, (u8 *)reply, if_version_length, 0, 0); - if (bytes_recv < if_version_length) { + if (bytes_recv < 0 || bytes_recv < if_version_length) { dev_err(bus->dev, "Could not read IF version\n"); ret = -EIO; goto err; -- cgit v1.2.3 From cf1ed2c59b982d892b7c4d25b3dea2421cc5aa08 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Wed, 11 Jul 2018 15:29:32 +0300 Subject: mei: bus: type promotion bug in mei_fwver() if __mei_cl_recv() returns a negative then "bytes_recv" type is promoted to a high positive value in comparison with size_t evaluated by MKHI_FWVER_LEN(1). It results in error condition not to be detected. Cc: Dan Carpenter Fixes: 9078ad92ef86 ("mei: expose fw version to sysfs") Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/bus-fixup.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/misc/mei/bus-fixup.c b/drivers/misc/mei/bus-fixup.c index 65e28be3c8cc..4c232f0eae90 100644 --- a/drivers/misc/mei/bus-fixup.c +++ b/drivers/misc/mei/bus-fixup.c @@ -181,7 +181,7 @@ static int mei_fwver(struct mei_cl_device *cldev) ret = 0; bytes_recv = __mei_cl_recv(cldev->cl, buf, sizeof(buf), 0, MKHI_RCV_TIMEOUT); - if (bytes_recv < MKHI_FWVER_LEN(1)) { + if (bytes_recv < 0 || bytes_recv < MKHI_FWVER_LEN(1)) { /* * Should be at least one version block, * error out if nothing found -- cgit v1.2.3 From a103af1b64d74853a5e08ca6c86aeb0e5c6ca4f1 Mon Sep 17 00:00:00 2001 From: Alexander Usyskin Date: Mon, 9 Jul 2018 12:21:44 +0300 Subject: mei: don't update offset in write MEI enables writes of complete messages only while read can be performed in parts, hence write should not update the file offset to not break interleaving partial reads with writes. Cc: Signed-off-by: Alexander Usyskin Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/main.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/misc/mei/main.c b/drivers/misc/mei/main.c index f690918f7817..302ba7a63bd2 100644 --- a/drivers/misc/mei/main.c +++ b/drivers/misc/mei/main.c @@ -312,7 +312,6 @@ static ssize_t mei_write(struct file *file, const char __user *ubuf, } } - *offset = 0; cb = mei_cl_alloc_cb(cl, length, MEI_FOP_WRITE, file); if (!cb) { rets = -ENOMEM; -- cgit v1.2.3 From de8774371cdc4c18cd118490e0d61eccd5f2c4d8 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Thu, 12 Jul 2018 17:10:08 +0300 Subject: mei: check for error returned from mei_hbuf_empty_slots() mei_hbuf_empty_slots() may return with an error in case of circular buffer overflow. This type of error may be caused only by a bug. However currently, the error won't be detected due signed type promotion in comparison to u32. We add explicit check for less then zero and explicit cast in comparison to suppress singn-compare warning. Reported-by: Dan Carpenter Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/client.c | 22 ++++++++++++++++------ drivers/misc/mei/hw-me.c | 5 ++++- drivers/misc/mei/hw-txe.c | 2 +- drivers/misc/mei/interrupt.c | 15 +++++++++++---- 4 files changed, 32 insertions(+), 12 deletions(-) diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c index 8d6197a88b54..f8fb7589192e 100644 --- a/drivers/misc/mei/client.c +++ b/drivers/misc/mei/client.c @@ -865,8 +865,10 @@ int mei_cl_irq_disconnect(struct mei_cl *cl, struct mei_cl_cb *cb, msg_slots = mei_data2slots(sizeof(struct hbm_client_connect_request)); slots = mei_hbuf_empty_slots(dev); + if (slots < 0) + return -EOVERFLOW; - if (slots < msg_slots) + if ((u32)slots < msg_slots) return -EMSGSIZE; ret = mei_cl_send_disconnect(cl, cb); @@ -1054,12 +1056,15 @@ int mei_cl_irq_connect(struct mei_cl *cl, struct mei_cl_cb *cb, int rets; msg_slots = mei_data2slots(sizeof(struct hbm_client_connect_request)); - slots = mei_hbuf_empty_slots(dev); if (mei_cl_is_other_connecting(cl)) return 0; - if (slots < msg_slots) + slots = mei_hbuf_empty_slots(dev); + if (slots < 0) + return -EOVERFLOW; + + if ((u32)slots < msg_slots) return -EMSGSIZE; rets = mei_cl_send_connect(cl, cb); @@ -1296,8 +1301,10 @@ int mei_cl_irq_notify(struct mei_cl *cl, struct mei_cl_cb *cb, msg_slots = mei_data2slots(sizeof(struct hbm_client_connect_request)); slots = mei_hbuf_empty_slots(dev); + if (slots < 0) + return -EOVERFLOW; - if (slots < msg_slots) + if ((u32)slots < msg_slots) return -EMSGSIZE; request = mei_cl_notify_fop2req(cb->fop_type); @@ -1573,6 +1580,9 @@ int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb, } slots = mei_hbuf_empty_slots(dev); + if (slots < 0) + return -EOVERFLOW; + len = buf->size - cb->buf_idx; msg_slots = mei_data2slots(len); @@ -1581,11 +1591,11 @@ int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb, mei_hdr.reserved = 0; mei_hdr.internal = cb->internal; - if (slots >= msg_slots) { + if ((u32)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) { + } else if ((u32)slots == dev->hbuf_depth) { msg_slots = slots; len = (slots * sizeof(u32)) - sizeof(struct mei_msg_hdr); mei_hdr.length = len; diff --git a/drivers/misc/mei/hw-me.c b/drivers/misc/mei/hw-me.c index 334ab02e1de2..a12b464bc0b4 100644 --- a/drivers/misc/mei/hw-me.c +++ b/drivers/misc/mei/hw-me.c @@ -540,8 +540,11 @@ static int mei_me_hbuf_write(struct mei_device *dev, empty_slots = mei_hbuf_empty_slots(dev); dev_dbg(dev->dev, "empty slots = %hu.\n", empty_slots); + if (empty_slots < 0) + return -EOVERFLOW; + dw_cnt = mei_data2slots(length); - if (empty_slots < 0 || dw_cnt > empty_slots) + if (dw_cnt > (u32)empty_slots) return -EMSGSIZE; mei_me_hcbww_write(dev, *((u32 *) header)); diff --git a/drivers/misc/mei/hw-txe.c b/drivers/misc/mei/hw-txe.c index c2c8993e2a51..4c1acf68089e 100644 --- a/drivers/misc/mei/hw-txe.c +++ b/drivers/misc/mei/hw-txe.c @@ -709,7 +709,7 @@ static int mei_txe_write(struct mei_device *dev, struct mei_txe_hw *hw = to_txe_hw(dev); unsigned long rem; unsigned long length; - int slots = dev->hbuf_depth; + u32 slots = dev->hbuf_depth; u32 *reg_buf = (u32 *)buf; u32 dw_cnt; int i; diff --git a/drivers/misc/mei/interrupt.c b/drivers/misc/mei/interrupt.c index b0b8f18a85e3..01990e339e76 100644 --- a/drivers/misc/mei/interrupt.c +++ b/drivers/misc/mei/interrupt.c @@ -173,10 +173,12 @@ static int mei_cl_irq_disconnect_rsp(struct mei_cl *cl, struct mei_cl_cb *cb, int slots; int ret; - slots = mei_hbuf_empty_slots(dev); msg_slots = mei_data2slots(sizeof(struct hbm_client_connect_response)); + slots = mei_hbuf_empty_slots(dev); + if (slots < 0) + return -EOVERFLOW; - if (slots < msg_slots) + if ((u32)slots < msg_slots) return -EMSGSIZE; ret = mei_hbm_cl_disconnect_rsp(dev, cl); @@ -208,8 +210,10 @@ static int mei_cl_irq_read(struct mei_cl *cl, struct mei_cl_cb *cb, msg_slots = mei_data2slots(sizeof(struct hbm_flow_control)); slots = mei_hbuf_empty_slots(dev); + if (slots < 0) + return -EOVERFLOW; - if (slots < msg_slots) + if ((u32)slots < msg_slots) return -EMSGSIZE; ret = mei_hbm_cl_flow_control_req(dev, cl); @@ -365,7 +369,10 @@ int mei_irq_write_handler(struct mei_device *dev, struct list_head *cmpl_list) return 0; slots = mei_hbuf_empty_slots(dev); - if (slots <= 0) + if (slots < 0) + return -EOVERFLOW; + + if (slots == 0) return -EMSGSIZE; /* complete all waiting for write CB */ -- cgit v1.2.3 From 44c98df01851c0d5941b06c60d1bd092bac088c3 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Thu, 12 Jul 2018 17:10:09 +0300 Subject: mei: use correct type for counter variable in for loops In for loops use same type for counter variable as has the limiting variable. drivers/misc/mei/bus-fixup.c:489:16: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] drivers/misc/mei/hw-txe.c:725:13: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] drivers/misc/mei/hw-txe.c:744:16: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/bus-fixup.c | 2 +- drivers/misc/mei/hw-me.c | 2 +- drivers/misc/mei/hw-txe.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/misc/mei/bus-fixup.c b/drivers/misc/mei/bus-fixup.c index 4c232f0eae90..2b8390447117 100644 --- a/drivers/misc/mei/bus-fixup.c +++ b/drivers/misc/mei/bus-fixup.c @@ -484,7 +484,7 @@ void mei_cl_bus_dev_fixup(struct mei_cl_device *cldev) { struct mei_fixup *f; const uuid_le *uuid = mei_me_cl_uuid(cldev->me_cl); - int i; + size_t i; for (i = 0; i < ARRAY_SIZE(mei_fixups); i++) { diff --git a/drivers/misc/mei/hw-me.c b/drivers/misc/mei/hw-me.c index a12b464bc0b4..5bbea13ab171 100644 --- a/drivers/misc/mei/hw-me.c +++ b/drivers/misc/mei/hw-me.c @@ -530,9 +530,9 @@ static int mei_me_hbuf_write(struct mei_device *dev, { unsigned long rem; unsigned long length = header->length; + unsigned long i; u32 *reg_buf = (u32 *)buf; u32 dw_cnt; - int i; int empty_slots; dev_dbg(dev->dev, MEI_HDR_FMT, MEI_HDR_PRM(header)); diff --git a/drivers/misc/mei/hw-txe.c b/drivers/misc/mei/hw-txe.c index 4c1acf68089e..a5e551ffb2dd 100644 --- a/drivers/misc/mei/hw-txe.c +++ b/drivers/misc/mei/hw-txe.c @@ -709,10 +709,10 @@ static int mei_txe_write(struct mei_device *dev, struct mei_txe_hw *hw = to_txe_hw(dev); unsigned long rem; unsigned long length; + unsigned long i; u32 slots = dev->hbuf_depth; u32 *reg_buf = (u32 *)buf; u32 dw_cnt; - int i; if (WARN_ON(!header || !buf)) return -EINVAL; -- cgit v1.2.3 From 5151e2b578e92f88d676ad78d36a2ef93d0a0dea Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Thu, 12 Jul 2018 17:10:10 +0300 Subject: mei: fix ssize_t to int assignment in read and write ops. Use ssize_t for rets variables in mei_write(), mei_read(), and mei_cl_write() as well as change the return type of mei_cl_write() to ssize_t, to prevent assignment of possible 64bit size_t to int 32 bit variable. As by product also eliminate warning drivers/misc/mei/client.c:1702:11: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/client.c | 22 +++++++++++----------- drivers/misc/mei/client.h | 2 +- drivers/misc/mei/main.c | 10 +++++----- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c index f8fb7589192e..5a673d09585f 100644 --- a/drivers/misc/mei/client.c +++ b/drivers/misc/mei/client.c @@ -1644,13 +1644,13 @@ err: * * Return: number of bytes sent on success, <0 on failure. */ -int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb) +ssize_t mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb) { struct mei_device *dev; struct mei_msg_data *buf; struct mei_msg_hdr mei_hdr; - int size; - int rets; + size_t len; + ssize_t rets; bool blocking; if (WARN_ON(!cl || !cl->dev)) @@ -1662,15 +1662,15 @@ int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb) dev = cl->dev; buf = &cb->buf; - size = buf->size; + len = buf->size; blocking = cb->blocking; - cl_dbg(dev, cl, "size=%d\n", size); + cl_dbg(dev, cl, "len=%zd\n", len); rets = pm_runtime_get(dev->dev); if (rets < 0 && rets != -EINPROGRESS) { pm_runtime_put_noidle(dev->dev); - cl_err(dev, cl, "rpm: get failed %d\n", rets); + cl_err(dev, cl, "rpm: get failed %zd\n", rets); goto free; } @@ -1689,21 +1689,21 @@ int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb) if (rets == 0) { cl_dbg(dev, cl, "No flow control credentials: not sending.\n"); - rets = size; + rets = len; goto out; } if (!mei_hbuf_acquire(dev)) { cl_dbg(dev, cl, "Cannot acquire the host buffer: not sending.\n"); - rets = size; + rets = len; goto out; } /* Check for a maximum length */ - if (size > mei_hbuf_max_len(dev)) { + if (len > mei_hbuf_max_len(dev)) { mei_hdr.length = mei_hbuf_max_len(dev); mei_hdr.msg_complete = 0; } else { - mei_hdr.length = size; + mei_hdr.length = len; mei_hdr.msg_complete = 1; } @@ -1745,7 +1745,7 @@ out: } } - rets = size; + rets = len; err: cl_dbg(dev, cl, "rpm: autosuspend\n"); pm_runtime_mark_last_busy(dev->dev); diff --git a/drivers/misc/mei/client.h b/drivers/misc/mei/client.h index 5371df4d8af3..64e318f589b4 100644 --- a/drivers/misc/mei/client.h +++ b/drivers/misc/mei/client.h @@ -202,7 +202,7 @@ int mei_cl_connect(struct mei_cl *cl, struct mei_me_client *me_cl, int mei_cl_irq_connect(struct mei_cl *cl, struct mei_cl_cb *cb, struct list_head *cmpl_list); int mei_cl_read_start(struct mei_cl *cl, size_t length, const struct file *fp); -int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb); +ssize_t mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb); int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb, struct list_head *cmpl_list); diff --git a/drivers/misc/mei/main.c b/drivers/misc/mei/main.c index 302ba7a63bd2..4d77a6ae183a 100644 --- a/drivers/misc/mei/main.c +++ b/drivers/misc/mei/main.c @@ -137,7 +137,7 @@ static ssize_t mei_read(struct file *file, char __user *ubuf, struct mei_device *dev; struct mei_cl_cb *cb = NULL; bool nonblock = !!(file->f_flags & O_NONBLOCK); - int rets; + ssize_t rets; if (WARN_ON(!cl || !cl->dev)) return -ENODEV; @@ -170,7 +170,7 @@ static ssize_t mei_read(struct file *file, char __user *ubuf, rets = mei_cl_read_start(cl, length, file); if (rets && rets != -EBUSY) { - cl_dbg(dev, cl, "mei start read failure status = %d\n", rets); + cl_dbg(dev, cl, "mei start read failure status = %zd\n", rets); goto out; } @@ -204,7 +204,7 @@ copy_buffer: /* now copy the data to user space */ if (cb->status) { rets = cb->status; - cl_dbg(dev, cl, "read operation failed %d\n", rets); + cl_dbg(dev, cl, "read operation failed %zd\n", rets); goto free; } @@ -236,7 +236,7 @@ free: *offset = 0; out: - cl_dbg(dev, cl, "end mei read rets = %d\n", rets); + cl_dbg(dev, cl, "end mei read rets = %zd\n", rets); mutex_unlock(&dev->device_lock); return rets; } @@ -256,7 +256,7 @@ static ssize_t mei_write(struct file *file, const char __user *ubuf, struct mei_cl *cl = file->private_data; struct mei_cl_cb *cb; struct mei_device *dev; - int rets; + ssize_t rets; if (WARN_ON(!cl || !cl->dev)) return -ENODEV; -- cgit v1.2.3 From 7bd50ccf001f77db236af51797579359b09e2354 Mon Sep 17 00:00:00 2001 From: Mathieu Poirier Date: Wed, 11 Jul 2018 13:40:11 -0600 Subject: coresight: etm3x: Don't use contextID with PID namespaces Tracers can trigger trace acquisition based on contextID value, something that isn't useful when PID namespaces are enabled. Indeed the PID value of a process has a different representation in the kernel and the PID namespace, making the feature confusing and potentially leaking internal kernel information. As such simply return an error when the feature is being used from a PID namespace other than the default one. Signed-off-by: Mathieu Poirier Reviewed-by: Kim Phillips Acked-by: "Eric W. Biederman" Signed-off-by: Greg Kroah-Hartman --- drivers/hwtracing/coresight/coresight-etm.h | 3 -- .../hwtracing/coresight/coresight-etm3x-sysfs.c | 43 +++++++++++++++++++--- drivers/hwtracing/coresight/coresight-etm3x.c | 4 +- 3 files changed, 38 insertions(+), 12 deletions(-) diff --git a/drivers/hwtracing/coresight/coresight-etm.h b/drivers/hwtracing/coresight/coresight-etm.h index e8b4549e30e2..79e1ad860d8a 100644 --- a/drivers/hwtracing/coresight/coresight-etm.h +++ b/drivers/hwtracing/coresight/coresight-etm.h @@ -168,8 +168,6 @@ * @seq_curr_state: current value of the sequencer register. * @ctxid_idx: index for the context ID registers. * @ctxid_pid: value for the context ID to trigger on. - * @ctxid_vpid: Virtual PID seen by users if PID namespace is enabled, otherwise - * the same value of ctxid_pid. * @ctxid_mask: mask applicable to all the context IDs. * @sync_freq: Synchronisation frequency. * @timestamp_event: Defines an event that requests the insertion @@ -202,7 +200,6 @@ struct etm_config { u32 seq_curr_state; u8 ctxid_idx; u32 ctxid_pid[ETM_MAX_CTXID_CMP]; - u32 ctxid_vpid[ETM_MAX_CTXID_CMP]; u32 ctxid_mask; u32 sync_freq; u32 timestamp_event; diff --git a/drivers/hwtracing/coresight/coresight-etm3x-sysfs.c b/drivers/hwtracing/coresight/coresight-etm3x-sysfs.c index 9435c1481f61..75487b3fad86 100644 --- a/drivers/hwtracing/coresight/coresight-etm3x-sysfs.c +++ b/drivers/hwtracing/coresight/coresight-etm3x-sysfs.c @@ -4,6 +4,7 @@ * Author: Mathieu Poirier */ +#include #include #include #include "coresight-etm.h" @@ -1025,8 +1026,15 @@ static ssize_t ctxid_pid_show(struct device *dev, struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); struct etm_config *config = &drvdata->config; + /* + * Don't use contextID tracing if coming from a PID namespace. See + * comment in ctxid_pid_store(). + */ + if (task_active_pid_ns(current) != &init_pid_ns) + return -EINVAL; + spin_lock(&drvdata->spinlock); - val = config->ctxid_vpid[config->ctxid_idx]; + val = config->ctxid_pid[config->ctxid_idx]; spin_unlock(&drvdata->spinlock); return sprintf(buf, "%#lx\n", val); @@ -1037,19 +1045,28 @@ static ssize_t ctxid_pid_store(struct device *dev, const char *buf, size_t size) { int ret; - unsigned long vpid, pid; + unsigned long pid; struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); struct etm_config *config = &drvdata->config; - ret = kstrtoul(buf, 16, &vpid); + /* + * When contextID tracing is enabled the tracers will insert the + * value found in the contextID register in the trace stream. But if + * a process is in a namespace the PID of that process as seen from the + * namespace won't be what the kernel sees, something that makes the + * feature confusing and can potentially leak kernel only information. + * As such refuse to use the feature if @current is not in the initial + * PID namespace. + */ + if (task_active_pid_ns(current) != &init_pid_ns) + return -EINVAL; + + ret = kstrtoul(buf, 16, &pid); if (ret) return ret; - pid = coresight_vpid_to_pid(vpid); - spin_lock(&drvdata->spinlock); config->ctxid_pid[config->ctxid_idx] = pid; - config->ctxid_vpid[config->ctxid_idx] = vpid; spin_unlock(&drvdata->spinlock); return size; @@ -1063,6 +1080,13 @@ static ssize_t ctxid_mask_show(struct device *dev, struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); struct etm_config *config = &drvdata->config; + /* + * Don't use contextID tracing if coming from a PID namespace. See + * comment in ctxid_pid_store(). + */ + if (task_active_pid_ns(current) != &init_pid_ns) + return -EINVAL; + val = config->ctxid_mask; return sprintf(buf, "%#lx\n", val); } @@ -1076,6 +1100,13 @@ static ssize_t ctxid_mask_store(struct device *dev, struct etm_drvdata *drvdata = dev_get_drvdata(dev->parent); struct etm_config *config = &drvdata->config; + /* + * Don't use contextID tracing if coming from a PID namespace. See + * comment in ctxid_pid_store(). + */ + if (task_active_pid_ns(current) != &init_pid_ns) + return -EINVAL; + ret = kstrtoul(buf, 16, &val); if (ret) return ret; diff --git a/drivers/hwtracing/coresight/coresight-etm3x.c b/drivers/hwtracing/coresight/coresight-etm3x.c index 15ed64d51a5b..7c74263c333d 100644 --- a/drivers/hwtracing/coresight/coresight-etm3x.c +++ b/drivers/hwtracing/coresight/coresight-etm3x.c @@ -230,10 +230,8 @@ void etm_set_default(struct etm_config *config) config->seq_curr_state = 0x0; config->ctxid_idx = 0x0; - for (i = 0; i < ETM_MAX_CTXID_CMP; i++) { + for (i = 0; i < ETM_MAX_CTXID_CMP; i++) config->ctxid_pid[i] = 0x0; - config->ctxid_vpid[i] = 0x0; - } config->ctxid_mask = 0x0; /* Setting default to 1024 as per TRM recommendation */ -- cgit v1.2.3 From 450367f06ef310af15f6424376ce9d5c7879c2d1 Mon Sep 17 00:00:00 2001 From: Mathieu Poirier Date: Wed, 11 Jul 2018 13:40:12 -0600 Subject: coresight: etm4x: Don't use contextID with PID namespaces As with ETM3x, the ETM4x tracers can trigger trace acquisition based on contextID value, something that isn't useful when PID namespaces are enabled. Indeed the PID value of a process has a different representation in the kernel and the PID namespace, making the feature confusing and potentially leaking internal kernel information. As such simply return an error when the feature is being used from a PID namespace other than the default one. Signed-off-by: Mathieu Poirier Reviewed-by: Kim Phillips Acked-by: "Eric W. Biederman" Signed-off-by: Greg Kroah-Hartman --- .../hwtracing/coresight/coresight-etm4x-sysfs.c | 47 +++++++++++++++++----- drivers/hwtracing/coresight/coresight-etm4x.h | 3 -- 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/drivers/hwtracing/coresight/coresight-etm4x-sysfs.c b/drivers/hwtracing/coresight/coresight-etm4x-sysfs.c index 4eb8da785ce0..a0365e23678e 100644 --- a/drivers/hwtracing/coresight/coresight-etm4x-sysfs.c +++ b/drivers/hwtracing/coresight/coresight-etm4x-sysfs.c @@ -4,6 +4,7 @@ * Author: Mathieu Poirier */ +#include #include #include #include "coresight-etm4x.h" @@ -250,10 +251,8 @@ static ssize_t reset_store(struct device *dev, } config->ctxid_idx = 0x0; - for (i = 0; i < drvdata->numcidc; i++) { + for (i = 0; i < drvdata->numcidc; i++) config->ctxid_pid[i] = 0x0; - config->ctxid_vpid[i] = 0x0; - } config->ctxid_mask0 = 0x0; config->ctxid_mask1 = 0x0; @@ -1637,9 +1636,16 @@ static ssize_t ctxid_pid_show(struct device *dev, struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); struct etmv4_config *config = &drvdata->config; + /* + * Don't use contextID tracing if coming from a PID namespace. See + * comment in ctxid_pid_store(). + */ + if (task_active_pid_ns(current) != &init_pid_ns) + return -EINVAL; + spin_lock(&drvdata->spinlock); idx = config->ctxid_idx; - val = (unsigned long)config->ctxid_vpid[idx]; + val = (unsigned long)config->ctxid_pid[idx]; spin_unlock(&drvdata->spinlock); return scnprintf(buf, PAGE_SIZE, "%#lx\n", val); } @@ -1649,10 +1655,22 @@ static ssize_t ctxid_pid_store(struct device *dev, const char *buf, size_t size) { u8 idx; - unsigned long vpid, pid; + unsigned long pid; struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); struct etmv4_config *config = &drvdata->config; + /* + * When contextID tracing is enabled the tracers will insert the + * value found in the contextID register in the trace stream. But if + * a process is in a namespace the PID of that process as seen from the + * namespace won't be what the kernel sees, something that makes the + * feature confusing and can potentially leak kernel only information. + * As such refuse to use the feature if @current is not in the initial + * PID namespace. + */ + if (task_active_pid_ns(current) != &init_pid_ns) + return -EINVAL; + /* * only implemented when ctxid tracing is enabled, i.e. at least one * ctxid comparator is implemented and ctxid is greater than 0 bits @@ -1660,15 +1678,12 @@ static ssize_t ctxid_pid_store(struct device *dev, */ if (!drvdata->ctxid_size || !drvdata->numcidc) return -EINVAL; - if (kstrtoul(buf, 16, &vpid)) + if (kstrtoul(buf, 16, &pid)) return -EINVAL; - pid = coresight_vpid_to_pid(vpid); - spin_lock(&drvdata->spinlock); idx = config->ctxid_idx; config->ctxid_pid[idx] = (u64)pid; - config->ctxid_vpid[idx] = (u64)vpid; spin_unlock(&drvdata->spinlock); return size; } @@ -1682,6 +1697,13 @@ static ssize_t ctxid_masks_show(struct device *dev, struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); struct etmv4_config *config = &drvdata->config; + /* + * Don't use contextID tracing if coming from a PID namespace. See + * comment in ctxid_pid_store(). + */ + if (task_active_pid_ns(current) != &init_pid_ns) + return -EINVAL; + spin_lock(&drvdata->spinlock); val1 = config->ctxid_mask0; val2 = config->ctxid_mask1; @@ -1698,6 +1720,13 @@ static ssize_t ctxid_masks_store(struct device *dev, struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent); struct etmv4_config *config = &drvdata->config; + /* + * Don't use contextID tracing if coming from a PID namespace. See + * comment in ctxid_pid_store(). + */ + if (task_active_pid_ns(current) != &init_pid_ns) + return -EINVAL; + /* * only implemented when ctxid tracing is enabled, i.e. at least one * ctxid comparator is implemented and ctxid is greater than 0 bits diff --git a/drivers/hwtracing/coresight/coresight-etm4x.h b/drivers/hwtracing/coresight/coresight-etm4x.h index b7c4a6f6c6b9..52786e9d8926 100644 --- a/drivers/hwtracing/coresight/coresight-etm4x.h +++ b/drivers/hwtracing/coresight/coresight-etm4x.h @@ -230,8 +230,6 @@ * @addr_type: Current status of the comparator register. * @ctxid_idx: Context ID index selector. * @ctxid_pid: Value of the context ID comparator. - * @ctxid_vpid: Virtual PID seen by users if PID namespace is enabled, otherwise - * the same value of ctxid_pid. * @ctxid_mask0:Context ID comparator mask for comparator 0-3. * @ctxid_mask1:Context ID comparator mask for comparator 4-7. * @vmid_idx: VM ID index selector. @@ -274,7 +272,6 @@ struct etmv4_config { u8 addr_type[ETM_MAX_SINGLE_ADDR_CMP]; u8 ctxid_idx; u64 ctxid_pid[ETMv4_MAX_CTXID_CMP]; - u64 ctxid_vpid[ETMv4_MAX_CTXID_CMP]; u32 ctxid_mask0; u32 ctxid_mask1; u8 vmid_idx; -- cgit v1.2.3 From 25b4a2b909dc9fdd141ee4239cf78d1617a9d91d Mon Sep 17 00:00:00 2001 From: Mathieu Poirier Date: Wed, 11 Jul 2018 13:40:13 -0600 Subject: coresight: Remove function coresight_vpid_to_pid() Now that we prevent users from using contextID tracing when PID namespaces are involved there is no client for function coresight_vpid_to_pid(). As such simply remove it. Signed-off-by: Mathieu Poirier Reviewed-by: Kim Phillips Acked-by: "Eric W. Biederman" Signed-off-by: Greg Kroah-Hartman --- include/linux/coresight.h | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/include/linux/coresight.h b/include/linux/coresight.h index c265e0468414..e5421b83e4e6 100644 --- a/include/linux/coresight.h +++ b/include/linux/coresight.h @@ -267,24 +267,4 @@ static inline struct coresight_platform_data *of_get_coresight_platform_data( struct device *dev, const struct device_node *node) { return NULL; } #endif -#ifdef CONFIG_PID_NS -static inline unsigned long -coresight_vpid_to_pid(unsigned long vpid) -{ - struct task_struct *task = NULL; - unsigned long pid = 0; - - rcu_read_lock(); - task = find_task_by_vpid(vpid); - if (task) - pid = task_pid_nr(task); - rcu_read_unlock(); - - return pid; -} -#else -static inline unsigned long -coresight_vpid_to_pid(unsigned long vpid) { return vpid; } -#endif - #endif -- cgit v1.2.3 From 5cedd22370a0a460b663c06de1fc10b4ba3c5d0b Mon Sep 17 00:00:00 2001 From: Suzuki K Poulose Date: Wed, 11 Jul 2018 13:40:14 -0600 Subject: coresight: ETM: Add support for Arm Cortex-A73 and Cortex-A35 Add ETM PIDs of the Arm cortex-A CPUs to the white list of ETMs. While at it add a helper macro to make it easier to add the new entries. Cc: Mathieu Poirier Signed-off-by: Suzuki K Poulose Signed-off-by: Mathieu Poirier Signed-off-by: Greg Kroah-Hartman --- drivers/hwtracing/coresight/coresight-etm4x.c | 31 ++++++++++++--------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/drivers/hwtracing/coresight/coresight-etm4x.c b/drivers/hwtracing/coresight/coresight-etm4x.c index 9bc04c50d45b..1d94ebec027b 100644 --- a/drivers/hwtracing/coresight/coresight-etm4x.c +++ b/drivers/hwtracing/coresight/coresight-etm4x.c @@ -1027,7 +1027,8 @@ static int etm4_probe(struct amba_device *adev, const struct amba_id *id) } pm_runtime_put(&adev->dev); - dev_info(dev, "%s initialized\n", (char *)id->data); + dev_info(dev, "CPU%d: ETM v%d.%d initialized\n", + drvdata->cpu, drvdata->arch >> 4, drvdata->arch & 0xf); if (boot_enable) { coresight_enable(drvdata->csdev); @@ -1045,23 +1046,19 @@ err_arch_supported: return ret; } +#define ETM4x_AMBA_ID(pid) \ + { \ + .id = pid, \ + .mask = 0x000fffff, \ + } + static const struct amba_id etm4_ids[] = { - { /* ETM 4.0 - Cortex-A53 */ - .id = 0x000bb95d, - .mask = 0x000fffff, - .data = "ETM 4.0", - }, - { /* ETM 4.0 - Cortex-A57 */ - .id = 0x000bb95e, - .mask = 0x000fffff, - .data = "ETM 4.0", - }, - { /* ETM 4.0 - A72, Maia, HiSilicon */ - .id = 0x000bb95a, - .mask = 0x000fffff, - .data = "ETM 4.0", - }, - { 0, 0}, + ETM4x_AMBA_ID(0x000bb95d), /* Cortex-A53 */ + ETM4x_AMBA_ID(0x000bb95e), /* Cortex-A57 */ + ETM4x_AMBA_ID(0x000bb95a), /* Cortex-A72 */ + ETM4x_AMBA_ID(0x000bb959), /* Cortex-A73 */ + ETM4x_AMBA_ID(0x000bb9da), /* Cortex-A35 */ + {}, }; static struct amba_driver etm4x_driver = { -- cgit v1.2.3 From 3495722a0093c80f66c020f90161983b46a1f5ee Mon Sep 17 00:00:00 2001 From: Suzuki K Poulose Date: Wed, 11 Jul 2018 13:40:15 -0600 Subject: coresight: tmc: Hide trace buffer handling for file read At the moment we adjust the buffer pointers for reading the trace data via misc device in the common code for ETF/ETB and ETR. Since we are going to change how we manage the buffer for ETR, let us move the buffer manipulation to the respective driver files, hiding it from the common code. We do so by adding type specific helpers for finding the length of data and the pointer to the buffer, for a given length at a file position. Cc: Mathieu Poirier Signed-off-by: Suzuki K Poulose Signed-off-by: Mathieu Poirier Signed-off-by: Greg Kroah-Hartman --- drivers/hwtracing/coresight/coresight-tmc-etf.c | 18 +++++++++++ drivers/hwtracing/coresight/coresight-tmc-etr.c | 34 ++++++++++++++++++++ drivers/hwtracing/coresight/coresight-tmc.c | 41 ++++++++++++++----------- drivers/hwtracing/coresight/coresight-tmc.h | 4 +++ 4 files changed, 79 insertions(+), 18 deletions(-) diff --git a/drivers/hwtracing/coresight/coresight-tmc-etf.c b/drivers/hwtracing/coresight/coresight-tmc-etf.c index 61d849b11c26..73160cd0e0d1 100644 --- a/drivers/hwtracing/coresight/coresight-tmc-etf.c +++ b/drivers/hwtracing/coresight/coresight-tmc-etf.c @@ -109,6 +109,24 @@ static void tmc_etf_disable_hw(struct tmc_drvdata *drvdata) CS_LOCK(drvdata->base); } +/* + * Return the available trace data in the buffer from @pos, with + * a maximum limit of @len, updating the @bufpp on where to + * find it. + */ +ssize_t tmc_etb_get_sysfs_trace(struct tmc_drvdata *drvdata, + loff_t pos, size_t len, char **bufpp) +{ + ssize_t actual = len; + + /* Adjust the len to available size @pos */ + if (pos + actual > drvdata->len) + actual = drvdata->len - pos; + if (actual > 0) + *bufpp = drvdata->buf + pos; + return actual; +} + static int tmc_enable_etf_sink_sysfs(struct coresight_device *csdev) { int ret = 0; diff --git a/drivers/hwtracing/coresight/coresight-tmc-etr.c b/drivers/hwtracing/coresight/coresight-tmc-etr.c index 02f747afa2ba..f88342d400ef 100644 --- a/drivers/hwtracing/coresight/coresight-tmc-etr.c +++ b/drivers/hwtracing/coresight/coresight-tmc-etr.c @@ -58,6 +58,40 @@ static void tmc_etr_enable_hw(struct tmc_drvdata *drvdata) CS_LOCK(drvdata->base); } +/* + * Return the available trace data in the buffer @pos, with a maximum + * limit of @len, also updating the @bufpp on where to find it. + */ +ssize_t tmc_etr_get_sysfs_trace(struct tmc_drvdata *drvdata, + loff_t pos, size_t len, char **bufpp) +{ + ssize_t actual = len; + char *bufp = drvdata->buf + pos; + char *bufend = (char *)(drvdata->vaddr + drvdata->size); + + /* Adjust the len to available size @pos */ + if (pos + actual > drvdata->len) + actual = drvdata->len - pos; + + if (actual <= 0) + return actual; + + /* + * Since we use a circular buffer, with trace data starting + * @drvdata->buf, possibly anywhere in the buffer @drvdata->vaddr, + * wrap the current @pos to within the buffer. + */ + if (bufp >= bufend) + bufp -= drvdata->size; + /* + * For simplicity, avoid copying over a wrapped around buffer. + */ + if ((bufp + actual) > bufend) + actual = bufend - bufp; + *bufpp = bufp; + return actual; +} + static void tmc_etr_dump_hw(struct tmc_drvdata *drvdata) { const u32 *barrier; diff --git a/drivers/hwtracing/coresight/coresight-tmc.c b/drivers/hwtracing/coresight/coresight-tmc.c index 456f122df74f..bb57e7f57eb5 100644 --- a/drivers/hwtracing/coresight/coresight-tmc.c +++ b/drivers/hwtracing/coresight/coresight-tmc.c @@ -123,35 +123,40 @@ static int tmc_open(struct inode *inode, struct file *file) return 0; } +static inline ssize_t tmc_get_sysfs_trace(struct tmc_drvdata *drvdata, + loff_t pos, size_t len, char **bufpp) +{ + switch (drvdata->config_type) { + case TMC_CONFIG_TYPE_ETB: + case TMC_CONFIG_TYPE_ETF: + return tmc_etb_get_sysfs_trace(drvdata, pos, len, bufpp); + case TMC_CONFIG_TYPE_ETR: + return tmc_etr_get_sysfs_trace(drvdata, pos, len, bufpp); + } + + return -EINVAL; +} + static ssize_t tmc_read(struct file *file, char __user *data, size_t len, loff_t *ppos) { + char *bufp; + ssize_t actual; struct tmc_drvdata *drvdata = container_of(file->private_data, struct tmc_drvdata, miscdev); - char *bufp = drvdata->buf + *ppos; - - if (*ppos + len > drvdata->len) - len = drvdata->len - *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; - } + actual = tmc_get_sysfs_trace(drvdata, *ppos, len, &bufp); + if (actual <= 0) + return 0; - if (copy_to_user(data, bufp, len)) { + if (copy_to_user(data, bufp, actual)) { dev_dbg(drvdata->dev, "%s: copy_to_user failed\n", __func__); return -EFAULT; } - *ppos += len; + *ppos += actual; + dev_dbg(drvdata->dev, "%zu bytes copied\n", actual); - dev_dbg(drvdata->dev, "%s: %zu bytes copied, %d bytes left\n", - __func__, len, (int)(drvdata->len - *ppos)); - return len; + return actual; } static int tmc_release(struct inode *inode, struct file *file) diff --git a/drivers/hwtracing/coresight/coresight-tmc.h b/drivers/hwtracing/coresight/coresight-tmc.h index dfaff077a7fc..1d7cd582919b 100644 --- a/drivers/hwtracing/coresight/coresight-tmc.h +++ b/drivers/hwtracing/coresight/coresight-tmc.h @@ -172,10 +172,14 @@ int tmc_read_unprepare_etb(struct tmc_drvdata *drvdata); extern const struct coresight_ops tmc_etb_cs_ops; extern const struct coresight_ops tmc_etf_cs_ops; +ssize_t tmc_etb_get_sysfs_trace(struct tmc_drvdata *drvdata, + loff_t pos, size_t len, char **bufpp); /* ETR functions */ int tmc_read_prepare_etr(struct tmc_drvdata *drvdata); int tmc_read_unprepare_etr(struct tmc_drvdata *drvdata); extern const struct coresight_ops tmc_etr_cs_ops; +ssize_t tmc_etr_get_sysfs_trace(struct tmc_drvdata *drvdata, + loff_t pos, size_t len, char **bufpp); #define TMC_REG_PAIR(name, lo_off, hi_off) \ -- cgit v1.2.3 From 0f728a7f9f8fed5dfd86a628d871d572cb91942c Mon Sep 17 00:00:00 2001 From: Suzuki K Poulose Date: Wed, 11 Jul 2018 13:40:16 -0600 Subject: coresight: tmc-etr: Do not clean trace buffer We zero out the entire trace buffer used for ETR before it is enabled, for helping with debugging. With the addition of scatter-gather mode, the buffer could be bigger and non-contiguous. Get rid of this step; if someone wants to debug, they can always add it as and when needed. Cc: Mathieu Poirier Signed-off-by: Suzuki K Poulose Signed-off-by: Mathieu Poirier Signed-off-by: Greg Kroah-Hartman --- drivers/hwtracing/coresight/coresight-tmc-etr.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/drivers/hwtracing/coresight/coresight-tmc-etr.c b/drivers/hwtracing/coresight/coresight-tmc-etr.c index f88342d400ef..1de05c9a01c8 100644 --- a/drivers/hwtracing/coresight/coresight-tmc-etr.c +++ b/drivers/hwtracing/coresight/coresight-tmc-etr.c @@ -13,9 +13,6 @@ static void tmc_etr_enable_hw(struct tmc_drvdata *drvdata) { u32 axictl, sts; - /* Zero out the memory to help with debug */ - memset(drvdata->vaddr, 0, drvdata->size); - CS_UNLOCK(drvdata->base); /* Wait for TMCSReady bit to be set */ @@ -340,9 +337,8 @@ int tmc_read_unprepare_etr(struct tmc_drvdata *drvdata) if (drvdata->mode == CS_MODE_SYSFS) { /* * The trace run will continue with the same allocated trace - * buffer. The trace buffer is cleared in tmc_etr_enable_hw(), - * so we don't have to explicitly clear it. Also, since the - * tracer is still enabled drvdata::buf can't be NULL. + * buffer. Since the tracer is still enabled drvdata::buf can't + * be NULL. */ tmc_etr_enable_hw(drvdata); } else { -- cgit v1.2.3 From ef32df53b73bd29d5bfa4af4fda287baa6dc6544 Mon Sep 17 00:00:00 2001 From: Suzuki K Poulose Date: Wed, 11 Jul 2018 13:40:17 -0600 Subject: coresight: tmc-etr: Disallow perf mode We don't support ETR in perf mode yet. So, don't even try to enable the hardware, even by mistake. Cc: Mathieu Poirier Signed-off-by: Suzuki K Poulose Signed-off-by: Mathieu Poirier Signed-off-by: Greg Kroah-Hartman --- drivers/hwtracing/coresight/coresight-tmc-etr.c | 28 ++----------------------- 1 file changed, 2 insertions(+), 26 deletions(-) diff --git a/drivers/hwtracing/coresight/coresight-tmc-etr.c b/drivers/hwtracing/coresight/coresight-tmc-etr.c index 1de05c9a01c8..18c9a184d31a 100644 --- a/drivers/hwtracing/coresight/coresight-tmc-etr.c +++ b/drivers/hwtracing/coresight/coresight-tmc-etr.c @@ -211,32 +211,8 @@ out: static int tmc_enable_etr_sink_perf(struct coresight_device *csdev) { - int ret = 0; - unsigned long flags; - struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); - - spin_lock_irqsave(&drvdata->spinlock, flags); - if (drvdata->reading) { - ret = -EINVAL; - goto out; - } - - /* - * In Perf mode there can be only one writer per sink. There - * is also no need to continue if the ETR is already operated - * from sysFS. - */ - if (drvdata->mode != CS_MODE_DISABLED) { - ret = -EINVAL; - goto out; - } - - drvdata->mode = CS_MODE_PERF; - tmc_etr_enable_hw(drvdata); -out: - spin_unlock_irqrestore(&drvdata->spinlock, flags); - - return ret; + /* We don't support perf mode yet ! */ + return -EINVAL; } static int tmc_enable_etr_sink(struct coresight_device *csdev, u32 mode) -- cgit v1.2.3 From 6f755e85c332de532d1bbcf1c9769aad08e8cb41 Mon Sep 17 00:00:00 2001 From: Suzuki K Poulose Date: Wed, 11 Jul 2018 13:40:18 -0600 Subject: coresight: Add helper for inserting synchronization packets Right now we open code filling the trace buffer with synchronization packets when the circular buffer wraps around in different drivers. Move this to a common place. While at it, clean up the barrier_pkt array to strip off the trailing '\0'. Cc: Mathieu Poirier Cc: Mike Leach Signed-off-by: Suzuki K Poulose Signed-off-by: Mathieu Poirier Signed-off-by: Greg Kroah-Hartman --- drivers/hwtracing/coresight/coresight-etb10.c | 12 ++++------- drivers/hwtracing/coresight/coresight-priv.h | 10 ++++++++- drivers/hwtracing/coresight/coresight-tmc-etf.c | 27 ++++++++----------------- drivers/hwtracing/coresight/coresight-tmc-etr.c | 13 +----------- drivers/hwtracing/coresight/coresight.c | 3 +-- 5 files changed, 23 insertions(+), 42 deletions(-) diff --git a/drivers/hwtracing/coresight/coresight-etb10.c b/drivers/hwtracing/coresight/coresight-etb10.c index 320d29df17e1..306119eaf16a 100644 --- a/drivers/hwtracing/coresight/coresight-etb10.c +++ b/drivers/hwtracing/coresight/coresight-etb10.c @@ -195,7 +195,6 @@ static void etb_dump_hw(struct etb_drvdata *drvdata) bool lost = false; int i; u8 *buf_ptr; - const u32 *barrier; u32 read_data, depth; u32 read_ptr, write_ptr; u32 frame_off, frame_endoff; @@ -226,19 +225,16 @@ static void etb_dump_hw(struct etb_drvdata *drvdata) depth = drvdata->buffer_depth; buf_ptr = drvdata->buf; - barrier = barrier_pkt; for (i = 0; i < depth; i++) { read_data = readl_relaxed(drvdata->base + ETB_RAM_READ_DATA_REG); - if (lost && *barrier) { - read_data = *barrier; - barrier++; - } - *(u32 *)buf_ptr = read_data; buf_ptr += 4; } + if (lost) + coresight_insert_barrier_packet(drvdata->buf); + if (frame_off) { buf_ptr -= (frame_endoff * 4); for (i = 0; i < frame_endoff; i++) { @@ -447,7 +443,7 @@ static void etb_update_buffer(struct coresight_device *csdev, buf_ptr = buf->data_pages[cur] + offset; read_data = readl_relaxed(drvdata->base + ETB_RAM_READ_DATA_REG); - if (lost && *barrier) { + if (lost && i < CORESIGHT_BARRIER_PKT_SIZE) { read_data = *barrier; barrier++; } diff --git a/drivers/hwtracing/coresight/coresight-priv.h b/drivers/hwtracing/coresight/coresight-priv.h index 0e5a74dae6a6..1a6cf3589866 100644 --- a/drivers/hwtracing/coresight/coresight-priv.h +++ b/drivers/hwtracing/coresight/coresight-priv.h @@ -57,7 +57,8 @@ static DEVICE_ATTR_RO(name) #define coresight_simple_reg64(type, name, lo_off, hi_off) \ __coresight_simple_func(type, NULL, name, lo_off, hi_off) -extern const u32 barrier_pkt[5]; +extern const u32 barrier_pkt[4]; +#define CORESIGHT_BARRIER_PKT_SIZE (sizeof(barrier_pkt)) enum etm_addr_type { ETM_ADDR_TYPE_NONE, @@ -91,6 +92,13 @@ struct cs_buffers { void **data_pages; }; +static inline void coresight_insert_barrier_packet(void *buf) +{ + if (buf) + memcpy(buf, barrier_pkt, CORESIGHT_BARRIER_PKT_SIZE); +} + + static inline void CS_LOCK(void __iomem *addr) { do { diff --git a/drivers/hwtracing/coresight/coresight-tmc-etf.c b/drivers/hwtracing/coresight/coresight-tmc-etf.c index 73160cd0e0d1..0549249f4b39 100644 --- a/drivers/hwtracing/coresight/coresight-tmc-etf.c +++ b/drivers/hwtracing/coresight/coresight-tmc-etf.c @@ -32,39 +32,28 @@ static void tmc_etb_enable_hw(struct tmc_drvdata *drvdata) static void tmc_etb_dump_hw(struct tmc_drvdata *drvdata) { - bool lost = false; char *bufp; - const u32 *barrier; - u32 read_data, status; + u32 read_data, lost; int i; - /* - * Get a hold of the status register and see if a wrap around - * has occurred. - */ - status = readl_relaxed(drvdata->base + TMC_STS); - if (status & TMC_STS_FULL) - lost = true; - + /* Check if the buffer wrapped around. */ + lost = readl_relaxed(drvdata->base + TMC_STS) & TMC_STS_FULL; bufp = drvdata->buf; drvdata->len = 0; - barrier = barrier_pkt; while (1) { for (i = 0; i < drvdata->memwidth; i++) { read_data = readl_relaxed(drvdata->base + TMC_RRD); if (read_data == 0xFFFFFFFF) - return; - - if (lost && *barrier) { - read_data = *barrier; - barrier++; - } - + goto done; memcpy(bufp, &read_data, 4); bufp += 4; drvdata->len += 4; } } +done: + if (lost) + coresight_insert_barrier_packet(drvdata->buf); + return; } static void tmc_etb_disable_hw(struct tmc_drvdata *drvdata) diff --git a/drivers/hwtracing/coresight/coresight-tmc-etr.c b/drivers/hwtracing/coresight/coresight-tmc-etr.c index 18c9a184d31a..04206ff7a67c 100644 --- a/drivers/hwtracing/coresight/coresight-tmc-etr.c +++ b/drivers/hwtracing/coresight/coresight-tmc-etr.c @@ -91,9 +91,7 @@ ssize_t tmc_etr_get_sysfs_trace(struct tmc_drvdata *drvdata, static void tmc_etr_dump_hw(struct tmc_drvdata *drvdata) { - const u32 *barrier; u32 val; - u32 *temp; u64 rwp; rwp = tmc_read_rwp(drvdata); @@ -106,16 +104,7 @@ static void tmc_etr_dump_hw(struct tmc_drvdata *drvdata) if (val & TMC_STS_FULL) { drvdata->buf = drvdata->vaddr + rwp - drvdata->paddr; drvdata->len = drvdata->size; - - barrier = barrier_pkt; - temp = (u32 *)drvdata->buf; - - while (*barrier) { - *temp = *barrier; - temp++; - barrier++; - } - + coresight_insert_barrier_packet(drvdata->buf); } else { drvdata->buf = drvdata->vaddr; drvdata->len = rwp - drvdata->paddr; diff --git a/drivers/hwtracing/coresight/coresight.c b/drivers/hwtracing/coresight/coresight.c index 29e834aab539..4969b329511c 100644 --- a/drivers/hwtracing/coresight/coresight.c +++ b/drivers/hwtracing/coresight/coresight.c @@ -51,8 +51,7 @@ static struct list_head *stm_path; * beginning of the data collected in a buffer. That way the decoder knows that * it needs to look for another sync sequence. */ -const u32 barrier_pkt[5] = {0x7fffffff, 0x7fffffff, - 0x7fffffff, 0x7fffffff, 0x0}; +const u32 barrier_pkt[4] = {0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff}; static int coresight_id_match(struct device *dev, void *data) { -- cgit v1.2.3 From ed2cfb2b3cd8f32419c0abea6cd4648ff99fca86 Mon Sep 17 00:00:00 2001 From: Suzuki K Poulose Date: Wed, 11 Jul 2018 13:40:19 -0600 Subject: dts: bindings: Restrict coresight tmc-etr scatter-gather mode We are about to add the support for ETR builtin scatter-gather mode for dealing with large amount of trace buffers. However, on some of the platforms, using the ETR SG mode can lock up the system due to the way the ETR is connected to the memory subsystem. In SG mode, the ETR performs READ from the scatter-gather table to fetch the next page and regular WRITE of trace data. If the READ operation doesn't complete(due to the memory subsystem issues, which we have seen on a couple of platforms) the trace WRITE cannot proceed leading to issues. So, we by default do not use the SG mode, unless it is known to be safe on the platform. We define a DT property for the TMC node to specify whether we have a proper SG mode. Cc: Mathieu Poirier Cc: Mike Leach Cc: Mark Rutland Cc: John Horley Cc: Robert Walker Cc: devicetree@vger.kernel.org Cc: frowand.list@gmail.com Reviewed-by: Rob Herring Signed-off-by: Suzuki K Poulose Signed-off-by: Mathieu Poirier Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/arm/coresight.txt | 2 ++ drivers/hwtracing/coresight/coresight-tmc.c | 9 ++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/arm/coresight.txt b/Documentation/devicetree/bindings/arm/coresight.txt index 15ac8e8dcfdf..603d3c62e8c7 100644 --- a/Documentation/devicetree/bindings/arm/coresight.txt +++ b/Documentation/devicetree/bindings/arm/coresight.txt @@ -86,6 +86,8 @@ its hardware characteristcs. * arm,buffer-size: size of contiguous buffer space for TMC ETR (embedded trace router) + * arm,scatter-gather: boolean. Indicates that the TMC-ETR can safely + use the SG mode on this system. Example: diff --git a/drivers/hwtracing/coresight/coresight-tmc.c b/drivers/hwtracing/coresight/coresight-tmc.c index bb57e7f57eb5..bc8fc860c473 100644 --- a/drivers/hwtracing/coresight/coresight-tmc.c +++ b/drivers/hwtracing/coresight/coresight-tmc.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -296,6 +297,12 @@ const struct attribute_group *coresight_tmc_groups[] = { NULL, }; +static inline bool tmc_etr_can_use_sg(struct tmc_drvdata *drvdata) +{ + return fwnode_property_present(drvdata->dev->fwnode, + "arm,scatter-gather"); +} + /* Detect and initialise the capabilities of a TMC ETR */ static int tmc_etr_setup_caps(struct tmc_drvdata *drvdata, u32 devid, void *dev_caps) @@ -305,7 +312,7 @@ static int tmc_etr_setup_caps(struct tmc_drvdata *drvdata, /* Set the unadvertised capabilities */ tmc_etr_init_caps(drvdata, (u32)(unsigned long)dev_caps); - if (!(devid & TMC_DEVID_NOSCAT)) + if (!(devid & TMC_DEVID_NOSCAT) && tmc_etr_can_use_sg(drvdata)) tmc_etr_set_cap(drvdata, TMC_ETR_SG); /* Check if the AXI address width is available */ -- cgit v1.2.3 From 99443ea19e8bb0fee5fb6f4ed9cec11fd825061f Mon Sep 17 00:00:00 2001 From: Suzuki K Poulose Date: Wed, 11 Jul 2018 13:40:20 -0600 Subject: coresight: Add generic TMC sg table framework This patch introduces a generic sg table data structure and associated operations. An SG table can be used to map a set of Data pages where the trace data could be stored by the TMC ETR. The information about the data pages could be stored in different formats, depending on the type of the underlying SG mechanism (e.g, TMC ETR SG vs Coresight CATU). The generic structure provides book keeping of the pages used for the data as well as the table contents. The table should be filled by the user of the infrastructure. A table can be created by specifying the number of data pages as well as the number of table pages required to hold the pointers, where the latter could be different for different types of tables. The pages are mapped in the appropriate dma data direction mode (i.e, DMA_TO_DEVICE for table pages and DMA_FROM_DEVICE for data pages). The framework can optionally accept a set of allocated data pages (e.g, perf ring buffer) and map them accordingly. The table and data pages are vmap'ed to allow easier access by the drivers. The framework also provides helpers to sync the data written to the pages with appropriate directions. This will be later used by the TMC ETR SG unit and CATU. Cc: Mathieu Poirier Signed-off-by: Suzuki K Poulose Signed-off-by: Mathieu Poirier Signed-off-by: Greg Kroah-Hartman --- drivers/hwtracing/coresight/coresight-tmc-etr.c | 268 ++++++++++++++++++++++++ drivers/hwtracing/coresight/coresight-tmc.h | 50 +++++ 2 files changed, 318 insertions(+) diff --git a/drivers/hwtracing/coresight/coresight-tmc-etr.c b/drivers/hwtracing/coresight/coresight-tmc-etr.c index 04206ff7a67c..402b06143e10 100644 --- a/drivers/hwtracing/coresight/coresight-tmc-etr.c +++ b/drivers/hwtracing/coresight/coresight-tmc-etr.c @@ -6,9 +6,277 @@ #include #include +#include #include "coresight-priv.h" #include "coresight-tmc.h" +/* + * tmc_pages_get_offset: Go through all the pages in the tmc_pages + * and map the device address @addr to an offset within the virtual + * contiguous buffer. + */ +static long +tmc_pages_get_offset(struct tmc_pages *tmc_pages, dma_addr_t addr) +{ + int i; + dma_addr_t page_start; + + for (i = 0; i < tmc_pages->nr_pages; i++) { + page_start = tmc_pages->daddrs[i]; + if (addr >= page_start && addr < (page_start + PAGE_SIZE)) + return i * PAGE_SIZE + (addr - page_start); + } + + return -EINVAL; +} + +/* + * tmc_pages_free : Unmap and free the pages used by tmc_pages. + * If the pages were not allocated in tmc_pages_alloc(), we would + * simply drop the refcount. + */ +static void tmc_pages_free(struct tmc_pages *tmc_pages, + struct device *dev, enum dma_data_direction dir) +{ + int i; + + for (i = 0; i < tmc_pages->nr_pages; i++) { + if (tmc_pages->daddrs && tmc_pages->daddrs[i]) + dma_unmap_page(dev, tmc_pages->daddrs[i], + PAGE_SIZE, dir); + if (tmc_pages->pages && tmc_pages->pages[i]) + __free_page(tmc_pages->pages[i]); + } + + kfree(tmc_pages->pages); + kfree(tmc_pages->daddrs); + tmc_pages->pages = NULL; + tmc_pages->daddrs = NULL; + tmc_pages->nr_pages = 0; +} + +/* + * tmc_pages_alloc : Allocate and map pages for a given @tmc_pages. + * If @pages is not NULL, the list of page virtual addresses are + * used as the data pages. The pages are then dma_map'ed for @dev + * with dma_direction @dir. + * + * Returns 0 upon success, else the error number. + */ +static int tmc_pages_alloc(struct tmc_pages *tmc_pages, + struct device *dev, int node, + enum dma_data_direction dir, void **pages) +{ + int i, nr_pages; + dma_addr_t paddr; + struct page *page; + + nr_pages = tmc_pages->nr_pages; + tmc_pages->daddrs = kcalloc(nr_pages, sizeof(*tmc_pages->daddrs), + GFP_KERNEL); + if (!tmc_pages->daddrs) + return -ENOMEM; + tmc_pages->pages = kcalloc(nr_pages, sizeof(*tmc_pages->pages), + GFP_KERNEL); + if (!tmc_pages->pages) { + kfree(tmc_pages->daddrs); + tmc_pages->daddrs = NULL; + return -ENOMEM; + } + + for (i = 0; i < nr_pages; i++) { + if (pages && pages[i]) { + page = virt_to_page(pages[i]); + /* Hold a refcount on the page */ + get_page(page); + } else { + page = alloc_pages_node(node, + GFP_KERNEL | __GFP_ZERO, 0); + } + paddr = dma_map_page(dev, page, 0, PAGE_SIZE, dir); + if (dma_mapping_error(dev, paddr)) + goto err; + tmc_pages->daddrs[i] = paddr; + tmc_pages->pages[i] = page; + } + return 0; +err: + tmc_pages_free(tmc_pages, dev, dir); + return -ENOMEM; +} + +static inline long +tmc_sg_get_data_page_offset(struct tmc_sg_table *sg_table, dma_addr_t addr) +{ + return tmc_pages_get_offset(&sg_table->data_pages, addr); +} + +static inline void tmc_free_table_pages(struct tmc_sg_table *sg_table) +{ + if (sg_table->table_vaddr) + vunmap(sg_table->table_vaddr); + tmc_pages_free(&sg_table->table_pages, sg_table->dev, DMA_TO_DEVICE); +} + +static void tmc_free_data_pages(struct tmc_sg_table *sg_table) +{ + if (sg_table->data_vaddr) + vunmap(sg_table->data_vaddr); + tmc_pages_free(&sg_table->data_pages, sg_table->dev, DMA_FROM_DEVICE); +} + +void tmc_free_sg_table(struct tmc_sg_table *sg_table) +{ + tmc_free_table_pages(sg_table); + tmc_free_data_pages(sg_table); +} + +/* + * Alloc pages for the table. Since this will be used by the device, + * allocate the pages closer to the device (i.e, dev_to_node(dev) + * rather than the CPU node). + */ +static int tmc_alloc_table_pages(struct tmc_sg_table *sg_table) +{ + int rc; + struct tmc_pages *table_pages = &sg_table->table_pages; + + rc = tmc_pages_alloc(table_pages, sg_table->dev, + dev_to_node(sg_table->dev), + DMA_TO_DEVICE, NULL); + if (rc) + return rc; + sg_table->table_vaddr = vmap(table_pages->pages, + table_pages->nr_pages, + VM_MAP, + PAGE_KERNEL); + if (!sg_table->table_vaddr) + rc = -ENOMEM; + else + sg_table->table_daddr = table_pages->daddrs[0]; + return rc; +} + +static int tmc_alloc_data_pages(struct tmc_sg_table *sg_table, void **pages) +{ + int rc; + + /* Allocate data pages on the node requested by the caller */ + rc = tmc_pages_alloc(&sg_table->data_pages, + sg_table->dev, sg_table->node, + DMA_FROM_DEVICE, pages); + if (!rc) { + sg_table->data_vaddr = vmap(sg_table->data_pages.pages, + sg_table->data_pages.nr_pages, + VM_MAP, + PAGE_KERNEL); + if (!sg_table->data_vaddr) + rc = -ENOMEM; + } + return rc; +} + +/* + * tmc_alloc_sg_table: Allocate and setup dma pages for the TMC SG table + * and data buffers. TMC writes to the data buffers and reads from the SG + * Table pages. + * + * @dev - Device to which page should be DMA mapped. + * @node - Numa node for mem allocations + * @nr_tpages - Number of pages for the table entries. + * @nr_dpages - Number of pages for Data buffer. + * @pages - Optional list of virtual address of pages. + */ +struct tmc_sg_table *tmc_alloc_sg_table(struct device *dev, + int node, + int nr_tpages, + int nr_dpages, + void **pages) +{ + long rc; + struct tmc_sg_table *sg_table; + + sg_table = kzalloc(sizeof(*sg_table), GFP_KERNEL); + if (!sg_table) + return ERR_PTR(-ENOMEM); + sg_table->data_pages.nr_pages = nr_dpages; + sg_table->table_pages.nr_pages = nr_tpages; + sg_table->node = node; + sg_table->dev = dev; + + rc = tmc_alloc_data_pages(sg_table, pages); + if (!rc) + rc = tmc_alloc_table_pages(sg_table); + if (rc) { + tmc_free_sg_table(sg_table); + kfree(sg_table); + return ERR_PTR(rc); + } + + return sg_table; +} + +/* + * tmc_sg_table_sync_data_range: Sync the data buffer written + * by the device from @offset upto a @size bytes. + */ +void tmc_sg_table_sync_data_range(struct tmc_sg_table *table, + u64 offset, u64 size) +{ + int i, index, start; + int npages = DIV_ROUND_UP(size, PAGE_SIZE); + struct device *dev = table->dev; + struct tmc_pages *data = &table->data_pages; + + start = offset >> PAGE_SHIFT; + for (i = start; i < (start + npages); i++) { + index = i % data->nr_pages; + dma_sync_single_for_cpu(dev, data->daddrs[index], + PAGE_SIZE, DMA_FROM_DEVICE); + } +} + +/* tmc_sg_sync_table: Sync the page table */ +void tmc_sg_table_sync_table(struct tmc_sg_table *sg_table) +{ + int i; + struct device *dev = sg_table->dev; + struct tmc_pages *table_pages = &sg_table->table_pages; + + for (i = 0; i < table_pages->nr_pages; i++) + dma_sync_single_for_device(dev, table_pages->daddrs[i], + PAGE_SIZE, DMA_TO_DEVICE); +} + +/* + * tmc_sg_table_get_data: Get the buffer pointer for data @offset + * in the SG buffer. The @bufpp is updated to point to the buffer. + * Returns : + * the length of linear data available at @offset. + * or + * <= 0 if no data is available. + */ +ssize_t tmc_sg_table_get_data(struct tmc_sg_table *sg_table, + u64 offset, size_t len, char **bufpp) +{ + size_t size; + int pg_idx = offset >> PAGE_SHIFT; + int pg_offset = offset & (PAGE_SIZE - 1); + struct tmc_pages *data_pages = &sg_table->data_pages; + + size = tmc_sg_table_buf_size(sg_table); + if (offset >= size) + return -EINVAL; + + /* Make sure we don't go beyond the end */ + len = (len < (size - offset)) ? len : size - offset; + /* Respect the page boundaries */ + len = (len < (PAGE_SIZE - pg_offset)) ? len : (PAGE_SIZE - pg_offset); + if (len > 0) + *bufpp = page_address(data_pages->pages[pg_idx]) + pg_offset; + return len; +} + static void tmc_etr_enable_hw(struct tmc_drvdata *drvdata) { u32 axictl, sts; diff --git a/drivers/hwtracing/coresight/coresight-tmc.h b/drivers/hwtracing/coresight/coresight-tmc.h index 1d7cd582919b..cdb668b4441d 100644 --- a/drivers/hwtracing/coresight/coresight-tmc.h +++ b/drivers/hwtracing/coresight/coresight-tmc.h @@ -7,6 +7,7 @@ #ifndef _CORESIGHT_TMC_H #define _CORESIGHT_TMC_H +#include #include #define TMC_RSZ 0x004 @@ -160,6 +161,38 @@ struct tmc_drvdata { u32 etr_caps; }; +/** + * struct tmc_pages - Collection of pages used for SG. + * @nr_pages: Number of pages in the list. + * @daddrs: Array of DMA'able page address. + * @pages: Array pages for the buffer. + */ +struct tmc_pages { + int nr_pages; + dma_addr_t *daddrs; + struct page **pages; +}; + +/* + * struct tmc_sg_table - Generic SG table for TMC + * @dev: Device for DMA allocations + * @table_vaddr: Contiguous Virtual address for PageTable + * @data_vaddr: Contiguous Virtual address for Data Buffer + * @table_daddr: DMA address of the PageTable base + * @node: Node for Page allocations + * @table_pages: List of pages & dma address for Table + * @data_pages: List of pages & dma address for Data + */ +struct tmc_sg_table { + struct device *dev; + void *table_vaddr; + void *data_vaddr; + dma_addr_t table_daddr; + int node; + struct tmc_pages table_pages; + struct tmc_pages data_pages; +}; + /* Generic functions */ void tmc_wait_for_tmcready(struct tmc_drvdata *drvdata); void tmc_flush_and_stop(struct tmc_drvdata *drvdata); @@ -215,4 +248,21 @@ static inline bool tmc_etr_has_cap(struct tmc_drvdata *drvdata, u32 cap) return !!(drvdata->etr_caps & cap); } +struct tmc_sg_table *tmc_alloc_sg_table(struct device *dev, + int node, + int nr_tpages, + int nr_dpages, + void **pages); +void tmc_free_sg_table(struct tmc_sg_table *sg_table); +void tmc_sg_table_sync_table(struct tmc_sg_table *sg_table); +void tmc_sg_table_sync_data_range(struct tmc_sg_table *table, + u64 offset, u64 size); +ssize_t tmc_sg_table_get_data(struct tmc_sg_table *sg_table, + u64 offset, size_t len, char **bufpp); +static inline unsigned long +tmc_sg_table_buf_size(struct tmc_sg_table *sg_table) +{ + return sg_table->data_pages.nr_pages << PAGE_SHIFT; +} + #endif -- cgit v1.2.3 From 260ec24b31a596361b617e6993ee9a31c43f278c Mon Sep 17 00:00:00 2001 From: Suzuki K Poulose Date: Wed, 11 Jul 2018 13:40:21 -0600 Subject: coresight: Add support for TMC ETR SG unit This patch adds support for setting up an SG table used by the TMC ETR inbuilt SG unit. The TMC ETR uses 4K page sized tables to hold pointers to the 4K data pages with the last entry in a table pointing to the next table with the entries, by kind of chaining. The 2 LSBs determine the type of the table entry, to one of : Normal - Points to a 4KB data page. Last - Points to a 4KB data page, but is the last entry in the page table. Link - Points to another 4KB table page with pointers to data. The code takes care of handling the system page size which could be different than 4K. So we could end up putting multiple ETR SG tables in a single system page, vice versa for the data pages. Cc: Mathieu Poirier Signed-off-by: Suzuki K Poulose Signed-off-by: Mathieu Poirier Signed-off-by: Greg Kroah-Hartman --- drivers/hwtracing/coresight/coresight-tmc-etr.c | 263 ++++++++++++++++++++++++ 1 file changed, 263 insertions(+) diff --git a/drivers/hwtracing/coresight/coresight-tmc-etr.c b/drivers/hwtracing/coresight/coresight-tmc-etr.c index 402b06143e10..54d5c0664277 100644 --- a/drivers/hwtracing/coresight/coresight-tmc-etr.c +++ b/drivers/hwtracing/coresight/coresight-tmc-etr.c @@ -10,6 +10,87 @@ #include "coresight-priv.h" #include "coresight-tmc.h" +/* + * The TMC ETR SG has a page size of 4K. The SG table contains pointers + * to 4KB buffers. However, the OS may use a PAGE_SIZE different from + * 4K (i.e, 16KB or 64KB). This implies that a single OS page could + * contain more than one SG buffer and tables. + * + * A table entry has the following format: + * + * ---Bit31------------Bit4-------Bit1-----Bit0-- + * | Address[39:12] | SBZ | Entry Type | + * ---------------------------------------------- + * + * Address: Bits [39:12] of a physical page address. Bits [11:0] are + * always zero. + * + * Entry type: + * b00 - Reserved. + * b01 - Last entry in the tables, points to 4K page buffer. + * b10 - Normal entry, points to 4K page buffer. + * b11 - Link. The address points to the base of next table. + */ + +typedef u32 sgte_t; + +#define ETR_SG_PAGE_SHIFT 12 +#define ETR_SG_PAGE_SIZE (1UL << ETR_SG_PAGE_SHIFT) +#define ETR_SG_PAGES_PER_SYSPAGE (PAGE_SIZE / ETR_SG_PAGE_SIZE) +#define ETR_SG_PTRS_PER_PAGE (ETR_SG_PAGE_SIZE / sizeof(sgte_t)) +#define ETR_SG_PTRS_PER_SYSPAGE (PAGE_SIZE / sizeof(sgte_t)) + +#define ETR_SG_ET_MASK 0x3 +#define ETR_SG_ET_LAST 0x1 +#define ETR_SG_ET_NORMAL 0x2 +#define ETR_SG_ET_LINK 0x3 + +#define ETR_SG_ADDR_SHIFT 4 + +#define ETR_SG_ENTRY(addr, type) \ + (sgte_t)((((addr) >> ETR_SG_PAGE_SHIFT) << ETR_SG_ADDR_SHIFT) | \ + (type & ETR_SG_ET_MASK)) + +#define ETR_SG_ADDR(entry) \ + (((dma_addr_t)(entry) >> ETR_SG_ADDR_SHIFT) << ETR_SG_PAGE_SHIFT) +#define ETR_SG_ET(entry) ((entry) & ETR_SG_ET_MASK) + +/* + * struct etr_sg_table : ETR SG Table + * @sg_table: Generic SG Table holding the data/table pages. + * @hwaddr: hwaddress used by the TMC, which is the base + * address of the table. + */ +struct etr_sg_table { + struct tmc_sg_table *sg_table; + dma_addr_t hwaddr; +}; + +/* + * tmc_etr_sg_table_entries: Total number of table entries required to map + * @nr_pages system pages. + * + * We need to map @nr_pages * ETR_SG_PAGES_PER_SYSPAGE data pages. + * Each TMC page can map (ETR_SG_PTRS_PER_PAGE - 1) buffer pointers, + * with the last entry pointing to another page of table entries. + * If we spill over to a new page for mapping 1 entry, we could as + * well replace the link entry of the previous page with the last entry. + */ +static inline unsigned long __attribute_const__ +tmc_etr_sg_table_entries(int nr_pages) +{ + unsigned long nr_sgpages = nr_pages * ETR_SG_PAGES_PER_SYSPAGE; + unsigned long nr_sglinks = nr_sgpages / (ETR_SG_PTRS_PER_PAGE - 1); + /* + * If we spill over to a new page for 1 entry, we could as well + * make it the LAST entry in the previous page, skipping the Link + * address. + */ + if (nr_sglinks && (nr_sgpages % (ETR_SG_PTRS_PER_PAGE - 1) < 2)) + nr_sglinks--; + return nr_sgpages + nr_sglinks; +} + /* * tmc_pages_get_offset: Go through all the pages in the tmc_pages * and map the device address @addr to an offset within the virtual @@ -277,6 +358,188 @@ ssize_t tmc_sg_table_get_data(struct tmc_sg_table *sg_table, return len; } +#ifdef ETR_SG_DEBUG +/* Map a dma address to virtual address */ +static unsigned long +tmc_sg_daddr_to_vaddr(struct tmc_sg_table *sg_table, + dma_addr_t addr, bool table) +{ + long offset; + unsigned long base; + struct tmc_pages *tmc_pages; + + if (table) { + tmc_pages = &sg_table->table_pages; + base = (unsigned long)sg_table->table_vaddr; + } else { + tmc_pages = &sg_table->data_pages; + base = (unsigned long)sg_table->data_vaddr; + } + + offset = tmc_pages_get_offset(tmc_pages, addr); + if (offset < 0) + return 0; + return base + offset; +} + +/* Dump the given sg_table */ +static void tmc_etr_sg_table_dump(struct etr_sg_table *etr_table) +{ + sgte_t *ptr; + int i = 0; + dma_addr_t addr; + struct tmc_sg_table *sg_table = etr_table->sg_table; + + ptr = (sgte_t *)tmc_sg_daddr_to_vaddr(sg_table, + etr_table->hwaddr, true); + while (ptr) { + addr = ETR_SG_ADDR(*ptr); + switch (ETR_SG_ET(*ptr)) { + case ETR_SG_ET_NORMAL: + dev_dbg(sg_table->dev, + "%05d: %p\t:[N] 0x%llx\n", i, ptr, addr); + ptr++; + break; + case ETR_SG_ET_LINK: + dev_dbg(sg_table->dev, + "%05d: *** %p\t:{L} 0x%llx ***\n", + i, ptr, addr); + ptr = (sgte_t *)tmc_sg_daddr_to_vaddr(sg_table, + addr, true); + break; + case ETR_SG_ET_LAST: + dev_dbg(sg_table->dev, + "%05d: ### %p\t:[L] 0x%llx ###\n", + i, ptr, addr); + return; + default: + dev_dbg(sg_table->dev, + "%05d: xxx %p\t:[INVALID] 0x%llx xxx\n", + i, ptr, addr); + return; + } + i++; + } + dev_dbg(sg_table->dev, "******* End of Table *****\n"); +} +#else +static inline void tmc_etr_sg_table_dump(struct etr_sg_table *etr_table) {} +#endif + +/* + * Populate the SG Table page table entries from table/data + * pages allocated. Each Data page has ETR_SG_PAGES_PER_SYSPAGE SG pages. + * So does a Table page. So we keep track of indices of the tables + * in each system page and move the pointers accordingly. + */ +#define INC_IDX_ROUND(idx, size) ((idx) = ((idx) + 1) % (size)) +static void tmc_etr_sg_table_populate(struct etr_sg_table *etr_table) +{ + dma_addr_t paddr; + int i, type, nr_entries; + int tpidx = 0; /* index to the current system table_page */ + int sgtidx = 0; /* index to the sg_table within the current syspage */ + int sgtentry = 0; /* the entry within the sg_table */ + int dpidx = 0; /* index to the current system data_page */ + int spidx = 0; /* index to the SG page within the current data page */ + sgte_t *ptr; /* pointer to the table entry to fill */ + struct tmc_sg_table *sg_table = etr_table->sg_table; + dma_addr_t *table_daddrs = sg_table->table_pages.daddrs; + dma_addr_t *data_daddrs = sg_table->data_pages.daddrs; + + nr_entries = tmc_etr_sg_table_entries(sg_table->data_pages.nr_pages); + /* + * Use the contiguous virtual address of the table to update entries. + */ + ptr = sg_table->table_vaddr; + /* + * Fill all the entries, except the last entry to avoid special + * checks within the loop. + */ + for (i = 0; i < nr_entries - 1; i++) { + if (sgtentry == ETR_SG_PTRS_PER_PAGE - 1) { + /* + * Last entry in a sg_table page is a link address to + * the next table page. If this sg_table is the last + * one in the system page, it links to the first + * sg_table in the next system page. Otherwise, it + * links to the next sg_table page within the system + * page. + */ + if (sgtidx == ETR_SG_PAGES_PER_SYSPAGE - 1) { + paddr = table_daddrs[tpidx + 1]; + } else { + paddr = table_daddrs[tpidx] + + (ETR_SG_PAGE_SIZE * (sgtidx + 1)); + } + type = ETR_SG_ET_LINK; + } else { + /* + * Update the indices to the data_pages to point to the + * next sg_page in the data buffer. + */ + type = ETR_SG_ET_NORMAL; + paddr = data_daddrs[dpidx] + spidx * ETR_SG_PAGE_SIZE; + if (!INC_IDX_ROUND(spidx, ETR_SG_PAGES_PER_SYSPAGE)) + dpidx++; + } + *ptr++ = ETR_SG_ENTRY(paddr, type); + /* + * Move to the next table pointer, moving the table page index + * if necessary + */ + if (!INC_IDX_ROUND(sgtentry, ETR_SG_PTRS_PER_PAGE)) { + if (!INC_IDX_ROUND(sgtidx, ETR_SG_PAGES_PER_SYSPAGE)) + tpidx++; + } + } + + /* Set up the last entry, which is always a data pointer */ + paddr = data_daddrs[dpidx] + spidx * ETR_SG_PAGE_SIZE; + *ptr++ = ETR_SG_ENTRY(paddr, ETR_SG_ET_LAST); +} + +/* + * tmc_init_etr_sg_table: Allocate a TMC ETR SG table, data buffer of @size and + * populate the table. + * + * @dev - Device pointer for the TMC + * @node - NUMA node where the memory should be allocated + * @size - Total size of the data buffer + * @pages - Optional list of page virtual address + */ +static struct etr_sg_table __maybe_unused * +tmc_init_etr_sg_table(struct device *dev, int node, + unsigned long size, void **pages) +{ + int nr_entries, nr_tpages; + int nr_dpages = size >> PAGE_SHIFT; + struct tmc_sg_table *sg_table; + struct etr_sg_table *etr_table; + + etr_table = kzalloc(sizeof(*etr_table), GFP_KERNEL); + if (!etr_table) + return ERR_PTR(-ENOMEM); + nr_entries = tmc_etr_sg_table_entries(nr_dpages); + nr_tpages = DIV_ROUND_UP(nr_entries, ETR_SG_PTRS_PER_SYSPAGE); + + sg_table = tmc_alloc_sg_table(dev, node, nr_tpages, nr_dpages, pages); + if (IS_ERR(sg_table)) { + kfree(etr_table); + return ERR_PTR(PTR_ERR(sg_table)); + } + + etr_table->sg_table = sg_table; + /* TMC should use table base address for DBA */ + etr_table->hwaddr = sg_table->table_daddr; + tmc_etr_sg_table_populate(etr_table); + /* Sync the table pages for the HW */ + tmc_sg_table_sync_table(sg_table); + tmc_etr_sg_table_dump(etr_table); + + return etr_table; +} + static void tmc_etr_enable_hw(struct tmc_drvdata *drvdata) { u32 axictl, sts; -- cgit v1.2.3 From 75f4e3619fe202c642080a798e84d05288522a22 Mon Sep 17 00:00:00 2001 From: Suzuki K Poulose Date: Wed, 11 Jul 2018 13:40:22 -0600 Subject: coresight: tmc-etr: Add transparent buffer management The TMC-ETR can use the target trace buffer in two different modes. Normal physically contiguous mode and a discontiguous list pages in Scatter-Gather mode. Also we have dedicated Coresight component, CATU (Coresight Address Translation Unit) to provide improved scatter-gather mode in Coresight SoC-600. This complicates the management of the buffer used for trace, depending on the mode in which ETR is configured. So, this patch adds a transparent layer for managing the ETR buffer which abstracts the basic operations on the buffer (alloc, free, sync and retrieve the data) and uses the mode specific helpers to do the actual operation. This also allows the ETR driver to choose the best mode for a given use case and adds the flexibility to fallback to a different mode, without duplicating the code. The patch also adds the "normal" flat memory mode and switches the sysfs driver to use the new layer. Cc: Mathieu Poirier Signed-off-by: Suzuki K Poulose Signed-off-by: Mathieu Poirier Signed-off-by: Greg Kroah-Hartman --- drivers/hwtracing/coresight/coresight-tmc-etr.c | 342 ++++++++++++++++++------ drivers/hwtracing/coresight/coresight-tmc.h | 55 +++- 2 files changed, 308 insertions(+), 89 deletions(-) diff --git a/drivers/hwtracing/coresight/coresight-tmc-etr.c b/drivers/hwtracing/coresight/coresight-tmc-etr.c index 54d5c0664277..fba659dd2ee0 100644 --- a/drivers/hwtracing/coresight/coresight-tmc-etr.c +++ b/drivers/hwtracing/coresight/coresight-tmc-etr.c @@ -6,10 +6,18 @@ #include #include +#include #include #include "coresight-priv.h" #include "coresight-tmc.h" +struct etr_flat_buf { + struct device *dev; + dma_addr_t daddr; + void *vaddr; + size_t size; +}; + /* * The TMC ETR SG has a page size of 4K. The SG table contains pointers * to 4KB buffers. However, the OS may use a PAGE_SIZE different from @@ -540,16 +548,207 @@ tmc_init_etr_sg_table(struct device *dev, int node, return etr_table; } +/* + * tmc_etr_alloc_flat_buf: Allocate a contiguous DMA buffer. + */ +static int tmc_etr_alloc_flat_buf(struct tmc_drvdata *drvdata, + struct etr_buf *etr_buf, int node, + void **pages) +{ + struct etr_flat_buf *flat_buf; + + /* We cannot reuse existing pages for flat buf */ + if (pages) + return -EINVAL; + + flat_buf = kzalloc(sizeof(*flat_buf), GFP_KERNEL); + if (!flat_buf) + return -ENOMEM; + + flat_buf->vaddr = dma_alloc_coherent(drvdata->dev, etr_buf->size, + &flat_buf->daddr, GFP_KERNEL); + if (!flat_buf->vaddr) { + kfree(flat_buf); + return -ENOMEM; + } + + flat_buf->size = etr_buf->size; + flat_buf->dev = drvdata->dev; + etr_buf->hwaddr = flat_buf->daddr; + etr_buf->mode = ETR_MODE_FLAT; + etr_buf->private = flat_buf; + return 0; +} + +static void tmc_etr_free_flat_buf(struct etr_buf *etr_buf) +{ + struct etr_flat_buf *flat_buf = etr_buf->private; + + if (flat_buf && flat_buf->daddr) + dma_free_coherent(flat_buf->dev, flat_buf->size, + flat_buf->vaddr, flat_buf->daddr); + kfree(flat_buf); +} + +static void tmc_etr_sync_flat_buf(struct etr_buf *etr_buf, u64 rrp, u64 rwp) +{ + /* + * Adjust the buffer to point to the beginning of the trace data + * and update the available trace data. + */ + etr_buf->offset = rrp - etr_buf->hwaddr; + if (etr_buf->full) + etr_buf->len = etr_buf->size; + else + etr_buf->len = rwp - rrp; +} + +static ssize_t tmc_etr_get_data_flat_buf(struct etr_buf *etr_buf, + u64 offset, size_t len, char **bufpp) +{ + struct etr_flat_buf *flat_buf = etr_buf->private; + + *bufpp = (char *)flat_buf->vaddr + offset; + /* + * tmc_etr_buf_get_data already adjusts the length to handle + * buffer wrapping around. + */ + return len; +} + +static const struct etr_buf_operations etr_flat_buf_ops = { + .alloc = tmc_etr_alloc_flat_buf, + .free = tmc_etr_free_flat_buf, + .sync = tmc_etr_sync_flat_buf, + .get_data = tmc_etr_get_data_flat_buf, +}; + +static const struct etr_buf_operations *etr_buf_ops[] = { + [ETR_MODE_FLAT] = &etr_flat_buf_ops, +}; + +static inline int tmc_etr_mode_alloc_buf(int mode, + struct tmc_drvdata *drvdata, + struct etr_buf *etr_buf, int node, + void **pages) +{ + int rc; + + switch (mode) { + case ETR_MODE_FLAT: + rc = etr_buf_ops[mode]->alloc(drvdata, etr_buf, node, pages); + if (!rc) + etr_buf->ops = etr_buf_ops[mode]; + return rc; + default: + return -EINVAL; + } +} + +/* + * tmc_alloc_etr_buf: Allocate a buffer use by ETR. + * @drvdata : ETR device details. + * @size : size of the requested buffer. + * @flags : Required properties for the buffer. + * @node : Node for memory allocations. + * @pages : An optional list of pages. + */ +static struct etr_buf *tmc_alloc_etr_buf(struct tmc_drvdata *drvdata, + ssize_t size, int flags, + int node, void **pages) +{ + int rc = 0; + struct etr_buf *etr_buf; + + etr_buf = kzalloc(sizeof(*etr_buf), GFP_KERNEL); + if (!etr_buf) + return ERR_PTR(-ENOMEM); + + etr_buf->size = size; + + rc = tmc_etr_mode_alloc_buf(ETR_MODE_FLAT, drvdata, + etr_buf, node, pages); + if (rc) { + kfree(etr_buf); + return ERR_PTR(rc); + } + + return etr_buf; +} + +static void tmc_free_etr_buf(struct etr_buf *etr_buf) +{ + WARN_ON(!etr_buf->ops || !etr_buf->ops->free); + etr_buf->ops->free(etr_buf); + kfree(etr_buf); +} + +/* + * tmc_etr_buf_get_data: Get the pointer the trace data at @offset + * with a maximum of @len bytes. + * Returns: The size of the linear data available @pos, with *bufpp + * updated to point to the buffer. + */ +static ssize_t tmc_etr_buf_get_data(struct etr_buf *etr_buf, + u64 offset, size_t len, char **bufpp) +{ + /* Adjust the length to limit this transaction to end of buffer */ + len = (len < (etr_buf->size - offset)) ? len : etr_buf->size - offset; + + return etr_buf->ops->get_data(etr_buf, (u64)offset, len, bufpp); +} + +static inline s64 +tmc_etr_buf_insert_barrier_packet(struct etr_buf *etr_buf, u64 offset) +{ + ssize_t len; + char *bufp; + + len = tmc_etr_buf_get_data(etr_buf, offset, + CORESIGHT_BARRIER_PKT_SIZE, &bufp); + if (WARN_ON(len <= CORESIGHT_BARRIER_PKT_SIZE)) + return -EINVAL; + coresight_insert_barrier_packet(bufp); + return offset + CORESIGHT_BARRIER_PKT_SIZE; +} + +/* + * tmc_sync_etr_buf: Sync the trace buffer availability with drvdata. + * Makes sure the trace data is synced to the memory for consumption. + * @etr_buf->offset will hold the offset to the beginning of the trace data + * within the buffer, with @etr_buf->len bytes to consume. + */ +static void tmc_sync_etr_buf(struct tmc_drvdata *drvdata) +{ + struct etr_buf *etr_buf = drvdata->etr_buf; + u64 rrp, rwp; + u32 status; + + rrp = tmc_read_rrp(drvdata); + rwp = tmc_read_rwp(drvdata); + status = readl_relaxed(drvdata->base + TMC_STS); + etr_buf->full = status & TMC_STS_FULL; + + WARN_ON(!etr_buf->ops || !etr_buf->ops->sync); + + etr_buf->ops->sync(etr_buf, rrp, rwp); + + /* Insert barrier packets at the beginning, if there was an overflow */ + if (etr_buf->full) + tmc_etr_buf_insert_barrier_packet(etr_buf, etr_buf->offset); +} + static void tmc_etr_enable_hw(struct tmc_drvdata *drvdata) { u32 axictl, sts; + struct etr_buf *etr_buf = drvdata->etr_buf; CS_UNLOCK(drvdata->base); /* Wait for TMCSReady bit to be set */ tmc_wait_for_tmcready(drvdata); - writel_relaxed(drvdata->size / 4, drvdata->base + TMC_RSZ); + writel_relaxed(etr_buf->size / 4, drvdata->base + TMC_RSZ); writel_relaxed(TMC_MODE_CIRCULAR_BUFFER, drvdata->base + TMC_MODE); axictl = readl_relaxed(drvdata->base + TMC_AXICTL); @@ -563,15 +762,15 @@ static void tmc_etr_enable_hw(struct tmc_drvdata *drvdata) } writel_relaxed(axictl, drvdata->base + TMC_AXICTL); - tmc_write_dba(drvdata, drvdata->paddr); + tmc_write_dba(drvdata, etr_buf->hwaddr); /* * If the TMC pointers must be programmed before the session, * we have to set it properly (i.e, RRP/RWP to base address and * STS to "not full"). */ if (tmc_etr_has_cap(drvdata, TMC_ETR_SAVE_RESTORE)) { - tmc_write_rrp(drvdata, drvdata->paddr); - tmc_write_rwp(drvdata, drvdata->paddr); + tmc_write_rrp(drvdata, etr_buf->hwaddr); + tmc_write_rwp(drvdata, etr_buf->hwaddr); sts = readl_relaxed(drvdata->base + TMC_STS) & ~TMC_STS_FULL; writel_relaxed(sts, drvdata->base + TMC_STS); } @@ -587,59 +786,48 @@ static void tmc_etr_enable_hw(struct tmc_drvdata *drvdata) } /* - * Return the available trace data in the buffer @pos, with a maximum - * limit of @len, also updating the @bufpp on where to find it. + * Return the available trace data in the buffer (starts at etr_buf->offset, + * limited by etr_buf->len) from @pos, with a maximum limit of @len, + * also updating the @bufpp on where to find it. Since the trace data + * starts at anywhere in the buffer, depending on the RRP, we adjust the + * @len returned to handle buffer wrapping around. */ ssize_t tmc_etr_get_sysfs_trace(struct tmc_drvdata *drvdata, loff_t pos, size_t len, char **bufpp) { + s64 offset; ssize_t actual = len; - char *bufp = drvdata->buf + pos; - char *bufend = (char *)(drvdata->vaddr + drvdata->size); - - /* Adjust the len to available size @pos */ - if (pos + actual > drvdata->len) - actual = drvdata->len - pos; + struct etr_buf *etr_buf = drvdata->etr_buf; + if (pos + actual > etr_buf->len) + actual = etr_buf->len - pos; if (actual <= 0) return actual; - /* - * Since we use a circular buffer, with trace data starting - * @drvdata->buf, possibly anywhere in the buffer @drvdata->vaddr, - * wrap the current @pos to within the buffer. - */ - if (bufp >= bufend) - bufp -= drvdata->size; - /* - * For simplicity, avoid copying over a wrapped around buffer. - */ - if ((bufp + actual) > bufend) - actual = bufend - bufp; - *bufpp = bufp; - return actual; + /* Compute the offset from which we read the data */ + offset = etr_buf->offset + pos; + if (offset >= etr_buf->size) + offset -= etr_buf->size; + return tmc_etr_buf_get_data(etr_buf, offset, actual, bufpp); } -static void tmc_etr_dump_hw(struct tmc_drvdata *drvdata) +static struct etr_buf * +tmc_etr_setup_sysfs_buf(struct tmc_drvdata *drvdata) { - u32 val; - u64 rwp; + return tmc_alloc_etr_buf(drvdata, drvdata->size, + 0, cpu_to_node(0), NULL); +} - rwp = tmc_read_rwp(drvdata); - val = readl_relaxed(drvdata->base + TMC_STS); +static void +tmc_etr_free_sysfs_buf(struct etr_buf *buf) +{ + if (buf) + tmc_free_etr_buf(buf); +} - /* - * Adjust the buffer to point to the beginning of the trace data - * and update the available trace data. - */ - if (val & TMC_STS_FULL) { - drvdata->buf = drvdata->vaddr + rwp - drvdata->paddr; - drvdata->len = drvdata->size; - coresight_insert_barrier_packet(drvdata->buf); - } else { - drvdata->buf = drvdata->vaddr; - drvdata->len = rwp - drvdata->paddr; - } +static void tmc_etr_sync_sysfs_buf(struct tmc_drvdata *drvdata) +{ + tmc_sync_etr_buf(drvdata); } static void tmc_etr_disable_hw(struct tmc_drvdata *drvdata) @@ -652,7 +840,8 @@ static void tmc_etr_disable_hw(struct tmc_drvdata *drvdata) * read before the TMC is disabled. */ if (drvdata->mode == CS_MODE_SYSFS) - tmc_etr_dump_hw(drvdata); + tmc_etr_sync_sysfs_buf(drvdata); + tmc_disable_hw(drvdata); CS_LOCK(drvdata->base); @@ -661,35 +850,32 @@ static void tmc_etr_disable_hw(struct tmc_drvdata *drvdata) static int tmc_enable_etr_sink_sysfs(struct coresight_device *csdev) { int ret = 0; - bool used = false; unsigned long flags; - void __iomem *vaddr = NULL; - dma_addr_t paddr = 0; struct tmc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); + struct etr_buf *new_buf = NULL, *free_buf = NULL; /* - * If we don't have a buffer release the lock and allocate memory. - * Otherwise keep the lock and move along. + * If we are enabling the ETR from disabled state, we need to make + * sure we have a buffer with the right size. The etr_buf is not reset + * immediately after we stop the tracing in SYSFS mode as we wait for + * the user to collect the data. We may be able to reuse the existing + * buffer, provided the size matches. Any allocation has to be done + * with the lock released. */ spin_lock_irqsave(&drvdata->spinlock, flags); - if (!drvdata->vaddr) { + if (!drvdata->etr_buf || (drvdata->etr_buf->size != drvdata->size)) { spin_unlock_irqrestore(&drvdata->spinlock, flags); - /* - * Contiguous memory can't be allocated while a spinlock is - * held. As such allocate memory here and free it if a buffer - * has already been allocated (from a previous session). - */ - vaddr = dma_alloc_coherent(drvdata->dev, drvdata->size, - &paddr, GFP_KERNEL); - if (!vaddr) - return -ENOMEM; + /* Allocate memory with the locks released */ + free_buf = new_buf = tmc_etr_setup_sysfs_buf(drvdata); + if (IS_ERR(new_buf)) + return PTR_ERR(new_buf); /* Let's try again */ spin_lock_irqsave(&drvdata->spinlock, flags); } - if (drvdata->reading) { + if (drvdata->reading || drvdata->mode == CS_MODE_PERF) { ret = -EBUSY; goto out; } @@ -697,21 +883,19 @@ static int tmc_enable_etr_sink_sysfs(struct coresight_device *csdev) /* * In sysFS mode we can have multiple writers per sink. Since this * sink is already enabled no memory is needed and the HW need not be - * touched. + * touched, even if the buffer size has changed. */ if (drvdata->mode == CS_MODE_SYSFS) goto out; /* - * If drvdata::vaddr == NULL, use the memory allocated above. - * Otherwise a buffer still exists from a previous session, so - * simply use that. + * If we don't have a buffer or it doesn't match the requested size, + * use the buffer allocated above. Otherwise reuse the existing buffer. */ - if (drvdata->vaddr == NULL) { - used = true; - drvdata->vaddr = vaddr; - drvdata->paddr = paddr; - drvdata->buf = drvdata->vaddr; + if (!drvdata->etr_buf || + (new_buf && drvdata->etr_buf->size != new_buf->size)) { + free_buf = drvdata->etr_buf; + drvdata->etr_buf = new_buf; } drvdata->mode = CS_MODE_SYSFS; @@ -720,8 +904,8 @@ out: spin_unlock_irqrestore(&drvdata->spinlock, flags); /* Free memory outside the spinlock if need be */ - if (!used && vaddr) - dma_free_coherent(drvdata->dev, drvdata->size, vaddr, paddr); + if (free_buf) + tmc_etr_free_sysfs_buf(free_buf); if (!ret) dev_info(drvdata->dev, "TMC-ETR enabled\n"); @@ -800,8 +984,8 @@ int tmc_read_prepare_etr(struct tmc_drvdata *drvdata) goto out; } - /* If drvdata::buf is NULL the trace data has been read already */ - if (drvdata->buf == NULL) { + /* If drvdata::etr_buf is NULL the trace data has been read already */ + if (drvdata->etr_buf == NULL) { ret = -EINVAL; goto out; } @@ -820,8 +1004,7 @@ out: int tmc_read_unprepare_etr(struct tmc_drvdata *drvdata) { unsigned long flags; - dma_addr_t paddr; - void __iomem *vaddr = NULL; + struct etr_buf *etr_buf = NULL; /* config types are set a boot time and never change */ if (WARN_ON_ONCE(drvdata->config_type != TMC_CONFIG_TYPE_ETR)) @@ -842,17 +1025,16 @@ int tmc_read_unprepare_etr(struct tmc_drvdata *drvdata) * The ETR is not tracing and the buffer was just read. * As such prepare to free the trace buffer. */ - vaddr = drvdata->vaddr; - paddr = drvdata->paddr; - drvdata->buf = drvdata->vaddr = NULL; + etr_buf = drvdata->etr_buf; + drvdata->etr_buf = NULL; } drvdata->reading = false; spin_unlock_irqrestore(&drvdata->spinlock, flags); /* Free allocated memory out side of the spinlock */ - if (vaddr) - dma_free_coherent(drvdata->dev, drvdata->size, vaddr, paddr); + if (etr_buf) + tmc_free_etr_buf(etr_buf); return 0; } diff --git a/drivers/hwtracing/coresight/coresight-tmc.h b/drivers/hwtracing/coresight/coresight-tmc.h index cdb668b4441d..bbd65e2107be 100644 --- a/drivers/hwtracing/coresight/coresight-tmc.h +++ b/drivers/hwtracing/coresight/coresight-tmc.h @@ -123,6 +123,34 @@ enum tmc_mem_intf_width { #define CORESIGHT_SOC_600_ETR_CAPS \ (TMC_ETR_SAVE_RESTORE | TMC_ETR_AXI_ARCACHE) +enum etr_mode { + ETR_MODE_FLAT, /* Uses contiguous flat buffer */ +}; + +struct etr_buf_operations; + +/** + * struct etr_buf - Details of the buffer used by ETR + * @mode : Mode of the ETR buffer, contiguous, Scatter Gather etc. + * @full : Trace data overflow + * @size : Size of the buffer. + * @hwaddr : Address to be programmed in the TMC:DBA{LO,HI} + * @offset : Offset of the trace data in the buffer for consumption. + * @len : Available trace data @buf (may round up to the beginning). + * @ops : ETR buffer operations for the mode. + * @private : Backend specific information for the buf + */ +struct etr_buf { + enum etr_mode mode; + bool full; + ssize_t size; + dma_addr_t hwaddr; + unsigned long offset; + s64 len; + const struct etr_buf_operations *ops; + void *private; +}; + /** * struct tmc_drvdata - specifics associated to an TMC component * @base: memory mapped base address for this component. @@ -130,11 +158,10 @@ enum tmc_mem_intf_width { * @csdev: component vitals needed by the framework. * @miscdev: specifics to handle "/dev/xyz.tmc" entry. * @spinlock: only one at a time pls. - * @buf: area of memory where trace data get sent. - * @paddr: DMA start location in RAM. - * @vaddr: virtual representation of @paddr. - * @size: trace buffer size. - * @len: size of the available trace. + * @buf: Snapshot of the trace data for ETF/ETB. + * @etr_buf: details of buffer used in TMC-ETR + * @len: size of the available trace for ETF/ETB. + * @size: trace buffer size for this TMC (common for all modes). * @mode: how this TMC is being used. * @config_type: TMC variant, must be of type @tmc_config_type. * @memwidth: width of the memory interface databus, in bytes. @@ -149,11 +176,12 @@ struct tmc_drvdata { struct miscdevice miscdev; spinlock_t spinlock; bool reading; - char *buf; - dma_addr_t paddr; - void __iomem *vaddr; - u32 size; + union { + char *buf; /* TMC ETB */ + struct etr_buf *etr_buf; /* TMC ETR */ + }; u32 len; + u32 size; u32 mode; enum tmc_config_type config_type; enum tmc_mem_intf_width memwidth; @@ -161,6 +189,15 @@ struct tmc_drvdata { u32 etr_caps; }; +struct etr_buf_operations { + int (*alloc)(struct tmc_drvdata *drvdata, struct etr_buf *etr_buf, + int node, void **pages); + void (*sync)(struct etr_buf *etr_buf, u64 rrp, u64 rwp); + ssize_t (*get_data)(struct etr_buf *etr_buf, u64 offset, size_t len, + char **bufpp); + void (*free)(struct etr_buf *etr_buf); +}; + /** * struct tmc_pages - Collection of pages used for SG. * @nr_pages: Number of pages in the list. -- cgit v1.2.3 From e8e3b771395d9b75ba5da409b7d20435ddb05a21 Mon Sep 17 00:00:00 2001 From: Suzuki K Poulose Date: Wed, 11 Jul 2018 13:40:23 -0600 Subject: coresight: tmc-etr buf: Add TMC scatter gather mode backend Add the support for Scatter-Gather mode to the etr-buf layer. Since we now have two different modes, we choose the backend based on a set of conditions, documented in the code. Cc: Mathieu Poirier Signed-off-by: Suzuki K Poulose Signed-off-by: Mathieu Poirier Signed-off-by: Greg Kroah-Hartman --- drivers/hwtracing/coresight/coresight-tmc-etr.c | 114 +++++++++++++++++++++++- drivers/hwtracing/coresight/coresight-tmc.h | 1 + 2 files changed, 111 insertions(+), 4 deletions(-) diff --git a/drivers/hwtracing/coresight/coresight-tmc-etr.c b/drivers/hwtracing/coresight/coresight-tmc-etr.c index fba659dd2ee0..6164eed0b5fe 100644 --- a/drivers/hwtracing/coresight/coresight-tmc-etr.c +++ b/drivers/hwtracing/coresight/coresight-tmc-etr.c @@ -516,7 +516,7 @@ static void tmc_etr_sg_table_populate(struct etr_sg_table *etr_table) * @size - Total size of the data buffer * @pages - Optional list of page virtual address */ -static struct etr_sg_table __maybe_unused * +static struct etr_sg_table * tmc_init_etr_sg_table(struct device *dev, int node, unsigned long size, void **pages) { @@ -623,8 +623,86 @@ static const struct etr_buf_operations etr_flat_buf_ops = { .get_data = tmc_etr_get_data_flat_buf, }; +/* + * tmc_etr_alloc_sg_buf: Allocate an SG buf @etr_buf. Setup the parameters + * appropriately. + */ +static int tmc_etr_alloc_sg_buf(struct tmc_drvdata *drvdata, + struct etr_buf *etr_buf, int node, + void **pages) +{ + struct etr_sg_table *etr_table; + + etr_table = tmc_init_etr_sg_table(drvdata->dev, node, + etr_buf->size, pages); + if (IS_ERR(etr_table)) + return -ENOMEM; + etr_buf->hwaddr = etr_table->hwaddr; + etr_buf->mode = ETR_MODE_ETR_SG; + etr_buf->private = etr_table; + return 0; +} + +static void tmc_etr_free_sg_buf(struct etr_buf *etr_buf) +{ + struct etr_sg_table *etr_table = etr_buf->private; + + if (etr_table) { + tmc_free_sg_table(etr_table->sg_table); + kfree(etr_table); + } +} + +static ssize_t tmc_etr_get_data_sg_buf(struct etr_buf *etr_buf, u64 offset, + size_t len, char **bufpp) +{ + struct etr_sg_table *etr_table = etr_buf->private; + + return tmc_sg_table_get_data(etr_table->sg_table, offset, len, bufpp); +} + +static void tmc_etr_sync_sg_buf(struct etr_buf *etr_buf, u64 rrp, u64 rwp) +{ + long r_offset, w_offset; + struct etr_sg_table *etr_table = etr_buf->private; + struct tmc_sg_table *table = etr_table->sg_table; + + /* Convert hw address to offset in the buffer */ + r_offset = tmc_sg_get_data_page_offset(table, rrp); + if (r_offset < 0) { + dev_warn(table->dev, + "Unable to map RRP %llx to offset\n", rrp); + etr_buf->len = 0; + return; + } + + w_offset = tmc_sg_get_data_page_offset(table, rwp); + if (w_offset < 0) { + dev_warn(table->dev, + "Unable to map RWP %llx to offset\n", rwp); + etr_buf->len = 0; + return; + } + + etr_buf->offset = r_offset; + if (etr_buf->full) + etr_buf->len = etr_buf->size; + else + etr_buf->len = ((w_offset < r_offset) ? etr_buf->size : 0) + + w_offset - r_offset; + tmc_sg_table_sync_data_range(table, r_offset, etr_buf->len); +} + +static const struct etr_buf_operations etr_sg_buf_ops = { + .alloc = tmc_etr_alloc_sg_buf, + .free = tmc_etr_free_sg_buf, + .sync = tmc_etr_sync_sg_buf, + .get_data = tmc_etr_get_data_sg_buf, +}; + static const struct etr_buf_operations *etr_buf_ops[] = { [ETR_MODE_FLAT] = &etr_flat_buf_ops, + [ETR_MODE_ETR_SG] = &etr_sg_buf_ops, }; static inline int tmc_etr_mode_alloc_buf(int mode, @@ -636,6 +714,7 @@ static inline int tmc_etr_mode_alloc_buf(int mode, switch (mode) { case ETR_MODE_FLAT: + case ETR_MODE_ETR_SG: rc = etr_buf_ops[mode]->alloc(drvdata, etr_buf, node, pages); if (!rc) etr_buf->ops = etr_buf_ops[mode]; @@ -657,17 +736,38 @@ static struct etr_buf *tmc_alloc_etr_buf(struct tmc_drvdata *drvdata, ssize_t size, int flags, int node, void **pages) { - int rc = 0; + int rc = -ENOMEM; + bool has_etr_sg, has_iommu; struct etr_buf *etr_buf; + has_etr_sg = tmc_etr_has_cap(drvdata, TMC_ETR_SG); + has_iommu = iommu_get_domain_for_dev(drvdata->dev); + etr_buf = kzalloc(sizeof(*etr_buf), GFP_KERNEL); if (!etr_buf) return ERR_PTR(-ENOMEM); etr_buf->size = size; - rc = tmc_etr_mode_alloc_buf(ETR_MODE_FLAT, drvdata, - etr_buf, node, pages); + /* + * If we have to use an existing list of pages, we cannot reliably + * use a contiguous DMA memory (even if we have an IOMMU). Otherwise, + * we use the contiguous DMA memory if at least one of the following + * conditions is true: + * a) The ETR cannot use Scatter-Gather. + * b) we have a backing IOMMU + * c) The requested memory size is smaller (< 1M). + * + * Fallback to available mechanisms. + * + */ + if (!pages && + (!has_etr_sg || has_iommu || size < SZ_1M)) + rc = tmc_etr_mode_alloc_buf(ETR_MODE_FLAT, drvdata, + etr_buf, node, pages); + if (rc && has_etr_sg) + rc = tmc_etr_mode_alloc_buf(ETR_MODE_ETR_SG, drvdata, + etr_buf, node, pages); if (rc) { kfree(etr_buf); return ERR_PTR(rc); @@ -761,6 +861,12 @@ static void tmc_etr_enable_hw(struct tmc_drvdata *drvdata) axictl |= TMC_AXICTL_ARCACHE_OS; } + if (etr_buf->mode == ETR_MODE_ETR_SG) { + if (WARN_ON(!tmc_etr_has_cap(drvdata, TMC_ETR_SG))) + return; + axictl |= TMC_AXICTL_SCT_GAT_MODE; + } + writel_relaxed(axictl, drvdata->base + TMC_AXICTL); tmc_write_dba(drvdata, etr_buf->hwaddr); /* diff --git a/drivers/hwtracing/coresight/coresight-tmc.h b/drivers/hwtracing/coresight/coresight-tmc.h index bbd65e2107be..e7456575bfc1 100644 --- a/drivers/hwtracing/coresight/coresight-tmc.h +++ b/drivers/hwtracing/coresight/coresight-tmc.h @@ -125,6 +125,7 @@ enum tmc_mem_intf_width { enum etr_mode { ETR_MODE_FLAT, /* Uses contiguous flat buffer */ + ETR_MODE_ETR_SG, /* Uses in-built TMC ETR SG mechanism */ }; struct etr_buf_operations; -- cgit v1.2.3 From c34cc23f1dcb77c66b5cd074f83dee61bfcbe567 Mon Sep 17 00:00:00 2001 From: Suzuki K Poulose Date: Wed, 11 Jul 2018 13:40:24 -0600 Subject: coresight: tmc: Add configuration support for trace buffer size Now that we can dynamically switch between contiguous memory and SG table depending on the trace buffer size, provide the support for selecting an appropriate buffer size. Cc: Mathieu Poirier Signed-off-by: Suzuki K Poulose Acked-by: Rob Herring Signed-off-by: Mathieu Poirier Signed-off-by: Greg Kroah-Hartman --- .../ABI/testing/sysfs-bus-coresight-devices-tmc | 8 ++++++ .../devicetree/bindings/arm/coresight.txt | 3 +- drivers/hwtracing/coresight/coresight-tmc.c | 33 ++++++++++++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/Documentation/ABI/testing/sysfs-bus-coresight-devices-tmc b/Documentation/ABI/testing/sysfs-bus-coresight-devices-tmc index 4fe677ed1305..ab49b9ac3bcb 100644 --- a/Documentation/ABI/testing/sysfs-bus-coresight-devices-tmc +++ b/Documentation/ABI/testing/sysfs-bus-coresight-devices-tmc @@ -83,3 +83,11 @@ KernelVersion: 4.7 Contact: Mathieu Poirier Description: (R) Indicates the capabilities of the Coresight TMC. The value is read directly from the DEVID register, 0xFC8, + +What: /sys/bus/coresight/devices/.tmc/buffer_size +Date: December 2018 +KernelVersion: 4.19 +Contact: Mathieu Poirier +Description: (RW) Size of the trace buffer for TMC-ETR when used in SYSFS + mode. Writable only for TMC-ETR configurations. The value + should be aligned to the kernel pagesize. diff --git a/Documentation/devicetree/bindings/arm/coresight.txt b/Documentation/devicetree/bindings/arm/coresight.txt index 603d3c62e8c7..9aa30a156b57 100644 --- a/Documentation/devicetree/bindings/arm/coresight.txt +++ b/Documentation/devicetree/bindings/arm/coresight.txt @@ -84,7 +84,8 @@ its hardware characteristcs. * Optional property for TMC: * arm,buffer-size: size of contiguous buffer space for TMC ETR - (embedded trace router) + (embedded trace router). This property is obsolete. The buffer size + can be configured dynamically via buffer_size property in sysfs. * arm,scatter-gather: boolean. Indicates that the TMC-ETR can safely use the SG mode on this system. diff --git a/drivers/hwtracing/coresight/coresight-tmc.c b/drivers/hwtracing/coresight/coresight-tmc.c index bc8fc860c473..1b817ec1192c 100644 --- a/drivers/hwtracing/coresight/coresight-tmc.c +++ b/drivers/hwtracing/coresight/coresight-tmc.c @@ -277,8 +277,41 @@ static ssize_t trigger_cntr_store(struct device *dev, } static DEVICE_ATTR_RW(trigger_cntr); +static ssize_t buffer_size_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct tmc_drvdata *drvdata = dev_get_drvdata(dev->parent); + + return sprintf(buf, "%#x\n", drvdata->size); +} + +static ssize_t buffer_size_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); + + /* Only permitted for TMC-ETRs */ + if (drvdata->config_type != TMC_CONFIG_TYPE_ETR) + return -EPERM; + + ret = kstrtoul(buf, 0, &val); + if (ret) + return ret; + /* The buffer size should be page aligned */ + if (val & (PAGE_SIZE - 1)) + return -EINVAL; + drvdata->size = val; + return size; +} + +static DEVICE_ATTR_RW(buffer_size); + static struct attribute *coresight_tmc_attrs[] = { &dev_attr_trigger_cntr.attr, + &dev_attr_buffer_size.attr, NULL, }; -- cgit v1.2.3 From a748ddd1132c79888390934216ef428e0b2f0ea8 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Wed, 11 Jul 2018 13:40:25 -0600 Subject: coresight: include vmalloc.h for vmap/vunmap The newly introduced code fails to build in some configurations unless we include the right headers: drivers/hwtracing/coresight/coresight-tmc-etr.c: In function 'tmc_free_table_pages': drivers/hwtracing/coresight/coresight-tmc-etr.c:206:3: error: implicit declaration of function 'vunmap'; did you mean 'iounmap'? [-Werror=implicit-function-declaration] Fixes: 79613ae8715a ("coresight: Add generic TMC sg table framework") Signed-off-by: Arnd Bergmann Signed-off-by: Mathieu Poirier Signed-off-by: Greg Kroah-Hartman --- drivers/hwtracing/coresight/coresight-tmc-etr.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/hwtracing/coresight/coresight-tmc-etr.c b/drivers/hwtracing/coresight/coresight-tmc-etr.c index 6164eed0b5fe..3556d9a849e9 100644 --- a/drivers/hwtracing/coresight/coresight-tmc-etr.c +++ b/drivers/hwtracing/coresight/coresight-tmc-etr.c @@ -8,6 +8,7 @@ #include #include #include +#include #include "coresight-priv.h" #include "coresight-tmc.h" -- cgit v1.2.3 From 00ea1970298677e31ed8f962bda663cfc4848481 Mon Sep 17 00:00:00 2001 From: Suzuki K Poulose Date: Wed, 11 Jul 2018 13:40:26 -0600 Subject: coresight: Fix check in coresight_tmc_etr_buf_insert_barrier_packet We request for "CORESIGHT_BARRIER_PKT_SIZE" length and we should be happy when we get that size. Cc: Mathieu Poirier Signed-off-by: Suzuki K Poulose Signed-off-by: Mathieu Poirier Signed-off-by: Greg Kroah-Hartman --- drivers/hwtracing/coresight/coresight-tmc-etr.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwtracing/coresight/coresight-tmc-etr.c b/drivers/hwtracing/coresight/coresight-tmc-etr.c index 3556d9a849e9..8af451230121 100644 --- a/drivers/hwtracing/coresight/coresight-tmc-etr.c +++ b/drivers/hwtracing/coresight/coresight-tmc-etr.c @@ -807,7 +807,7 @@ tmc_etr_buf_insert_barrier_packet(struct etr_buf *etr_buf, u64 offset) len = tmc_etr_buf_get_data(etr_buf, offset, CORESIGHT_BARRIER_PKT_SIZE, &bufp); - if (WARN_ON(len <= CORESIGHT_BARRIER_PKT_SIZE)) + if (WARN_ON(len < CORESIGHT_BARRIER_PKT_SIZE)) return -EINVAL; coresight_insert_barrier_packet(bufp); return offset + CORESIGHT_BARRIER_PKT_SIZE; -- cgit v1.2.3 From 575694978247b7cc1588ec233bde3fbf66db9e50 Mon Sep 17 00:00:00 2001 From: Suzuki K Poulose Date: Wed, 11 Jul 2018 13:40:27 -0600 Subject: coresight: Cleanup platform description data Nobody uses the "clk" field in struct coresight_platform_data. Remove it. Cc: Mathieu Poirier Signed-off-by: Suzuki K Poulose Signed-off-by: Mathieu Poirier Signed-off-by: Greg Kroah-Hartman --- include/linux/coresight.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/include/linux/coresight.h b/include/linux/coresight.h index e5421b83e4e6..69a5c9f82469 100644 --- a/include/linux/coresight.h +++ b/include/linux/coresight.h @@ -87,7 +87,6 @@ struct coresight_dev_subtype { * @child_ports:child component port number the current component is connected to. * @nr_outport: number of output ports for this component. - * @clk: The clock this component is associated to. */ struct coresight_platform_data { int cpu; @@ -97,7 +96,6 @@ struct coresight_platform_data { const char **child_names; int *child_ports; int nr_outport; - struct clk *clk; }; /** -- cgit v1.2.3 From fe470f5f7f684ed15bc49b6183a64237547910ff Mon Sep 17 00:00:00 2001 From: Suzuki K Poulose Date: Wed, 11 Jul 2018 13:40:28 -0600 Subject: coresight: Handle errors in finding input/output ports If we fail to find the input / output port for a LINK component while enabling a path, we should fail gracefully rather than assuming port "0". Cc: Mathieu Poirier Signed-off-by: Suzuki K Poulose Signed-off-by: Mathieu Poirier Signed-off-by: Greg Kroah-Hartman --- drivers/hwtracing/coresight/coresight.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/hwtracing/coresight/coresight.c b/drivers/hwtracing/coresight/coresight.c index 4969b329511c..7f2b478a048f 100644 --- a/drivers/hwtracing/coresight/coresight.c +++ b/drivers/hwtracing/coresight/coresight.c @@ -107,7 +107,7 @@ static int coresight_find_link_inport(struct coresight_device *csdev, dev_err(&csdev->dev, "couldn't find inport, parent: %s, child: %s\n", dev_name(&parent->dev), dev_name(&csdev->dev)); - return 0; + return -ENODEV; } static int coresight_find_link_outport(struct coresight_device *csdev, @@ -125,7 +125,7 @@ static int coresight_find_link_outport(struct coresight_device *csdev, dev_err(&csdev->dev, "couldn't find outport, parent: %s, child: %s\n", dev_name(&csdev->dev), dev_name(&child->dev)); - return 0; + return -ENODEV; } static int coresight_enable_sink(struct coresight_device *csdev, u32 mode) @@ -178,6 +178,9 @@ static int coresight_enable_link(struct coresight_device *csdev, else refport = 0; + if (refport < 0) + return refport; + if (atomic_inc_return(&csdev->refcnt[refport]) == 1) { if (link_ops(csdev)->enable) { ret = link_ops(csdev)->enable(csdev, inport, outport); -- cgit v1.2.3 From 00b78e8b7b003c686a8570df7d231787f514226c Mon Sep 17 00:00:00 2001 From: Suzuki K Poulose Date: Wed, 11 Jul 2018 13:40:29 -0600 Subject: coresight: Cleanup device subtype struct Clean up our struct a little bit by using a union instead of a struct for tracking the subtype of a device. Cc: Mathieu Poirier Signed-off-by: Suzuki K Poulose Signed-off-by: Mathieu Poirier Signed-off-by: Greg Kroah-Hartman --- include/linux/coresight.h | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/include/linux/coresight.h b/include/linux/coresight.h index 69a5c9f82469..7c188c2a5b3c 100644 --- a/include/linux/coresight.h +++ b/include/linux/coresight.h @@ -63,17 +63,20 @@ enum coresight_dev_subtype_source { }; /** - * struct coresight_dev_subtype - further characterisation of a type + * union coresight_dev_subtype - further characterisation of a type * @sink_subtype: type of sink this component is, as defined - by @coresight_dev_subtype_sink. + * by @coresight_dev_subtype_sink. * @link_subtype: type of link this component is, as defined - by @coresight_dev_subtype_link. + * by @coresight_dev_subtype_link. * @source_subtype: type of source this component is, as defined - by @coresight_dev_subtype_source. + * by @coresight_dev_subtype_source. */ -struct coresight_dev_subtype { - enum coresight_dev_subtype_sink sink_subtype; - enum coresight_dev_subtype_link link_subtype; +union coresight_dev_subtype { + /* We have some devices which acts as LINK and SINK */ + struct { + enum coresight_dev_subtype_sink sink_subtype; + enum coresight_dev_subtype_link link_subtype; + }; enum coresight_dev_subtype_source source_subtype; }; @@ -111,7 +114,7 @@ struct coresight_platform_data { */ struct coresight_desc { enum coresight_dev_type type; - struct coresight_dev_subtype subtype; + union coresight_dev_subtype subtype; const struct coresight_ops *ops; struct coresight_platform_data *pdata; struct device *dev; @@ -155,7 +158,7 @@ struct coresight_device { int nr_inport; int nr_outport; enum coresight_dev_type type; - struct coresight_dev_subtype subtype; + union coresight_dev_subtype subtype; const struct coresight_ops *ops; struct device dev; atomic_t *refcnt; -- cgit v1.2.3 From 8a091d847cecd1a3656172cb79b53a19e90b8c4f Mon Sep 17 00:00:00 2001 From: Suzuki K Poulose Date: Wed, 11 Jul 2018 13:40:30 -0600 Subject: coresight: Add helper device type Add a new coresight device type, which do not belong to any of the existing types, i.e, source, sink, link etc. A helper device could be connected to a coresight device, which could augment the functionality of the coresight device. This is intended to cover Coresight Address Translation Unit (CATU) devices, which provide improved Scatter Gather mechanism for TMC ETR. The idea is that the helper device could be controlled by the driver of the device it is attached to (in this case ETR), transparent to the generic coresight driver (and paths). The operations include enable(), disable(), both of which could accept a device specific "data" which the driving device and the helper device could share. Since they don't appear in the coresight "path" tracked by software, we have to ensure that they are powered up/down whenever the master device is turned on. Cc: Mathieu Poirier Signed-off-by: Suzuki K Poulose Signed-off-by: Mathieu Poirier Signed-off-by: Greg Kroah-Hartman --- drivers/hwtracing/coresight/coresight.c | 43 +++++++++++++++++++++++++++++++-- include/linux/coresight.h | 24 ++++++++++++++++++ 2 files changed, 65 insertions(+), 2 deletions(-) diff --git a/drivers/hwtracing/coresight/coresight.c b/drivers/hwtracing/coresight/coresight.c index 7f2b478a048f..3e07fd335f8c 100644 --- a/drivers/hwtracing/coresight/coresight.c +++ b/drivers/hwtracing/coresight/coresight.c @@ -425,6 +425,42 @@ struct coresight_device *coresight_get_enabled_sink(bool deactivate) return dev ? to_coresight_device(dev) : NULL; } +/* + * coresight_grab_device - Power up this device and any of the helper + * devices connected to it for trace operation. Since the helper devices + * don't appear on the trace path, they should be handled along with the + * the master device. + */ +static void coresight_grab_device(struct coresight_device *csdev) +{ + int i; + + for (i = 0; i < csdev->nr_outport; i++) { + struct coresight_device *child = csdev->conns[i].child_dev; + + if (child && child->type == CORESIGHT_DEV_TYPE_HELPER) + pm_runtime_get_sync(child->dev.parent); + } + pm_runtime_get_sync(csdev->dev.parent); +} + +/* + * coresight_drop_device - Release this device and any of the helper + * devices connected to it. + */ +static void coresight_drop_device(struct coresight_device *csdev) +{ + int i; + + pm_runtime_put(csdev->dev.parent); + for (i = 0; i < csdev->nr_outport; i++) { + struct coresight_device *child = csdev->conns[i].child_dev; + + if (child && child->type == CORESIGHT_DEV_TYPE_HELPER) + pm_runtime_put(child->dev.parent); + } +} + /** * _coresight_build_path - recursively build a path from a @csdev to a sink. * @csdev: The device to start from. @@ -473,9 +509,9 @@ out: if (!node) return -ENOMEM; + coresight_grab_device(csdev); node->csdev = csdev; list_add(&node->link, path); - pm_runtime_get_sync(csdev->dev.parent); return 0; } @@ -519,7 +555,7 @@ void coresight_release_path(struct list_head *path) list_for_each_entry_safe(nd, next, path, link) { csdev = nd->csdev; - pm_runtime_put_sync(csdev->dev.parent); + coresight_drop_device(csdev); list_del(&nd->link); kfree(nd); } @@ -770,6 +806,9 @@ static struct device_type coresight_dev_type[] = { .name = "source", .groups = coresight_source_groups, }, + { + .name = "helper", + }, }; static void coresight_device_release(struct device *dev) diff --git a/include/linux/coresight.h b/include/linux/coresight.h index 7c188c2a5b3c..3d40a2b135f1 100644 --- a/include/linux/coresight.h +++ b/include/linux/coresight.h @@ -40,6 +40,7 @@ enum coresight_dev_type { CORESIGHT_DEV_TYPE_LINK, CORESIGHT_DEV_TYPE_LINKSINK, CORESIGHT_DEV_TYPE_SOURCE, + CORESIGHT_DEV_TYPE_HELPER, }; enum coresight_dev_subtype_sink { @@ -62,6 +63,10 @@ enum coresight_dev_subtype_source { CORESIGHT_DEV_SUBTYPE_SOURCE_SOFTWARE, }; +enum coresight_dev_subtype_helper { + CORESIGHT_DEV_SUBTYPE_HELPER_NONE, +}; + /** * union coresight_dev_subtype - further characterisation of a type * @sink_subtype: type of sink this component is, as defined @@ -70,6 +75,8 @@ enum coresight_dev_subtype_source { * by @coresight_dev_subtype_link. * @source_subtype: type of source this component is, as defined * by @coresight_dev_subtype_source. + * @helper_subtype: type of helper this component is, as defined + * by @coresight_dev_subtype_helper. */ union coresight_dev_subtype { /* We have some devices which acts as LINK and SINK */ @@ -78,6 +85,7 @@ union coresight_dev_subtype { enum coresight_dev_subtype_link link_subtype; }; enum coresight_dev_subtype_source source_subtype; + enum coresight_dev_subtype_helper helper_subtype; }; /** @@ -172,6 +180,7 @@ struct coresight_device { #define source_ops(csdev) csdev->ops->source_ops #define sink_ops(csdev) csdev->ops->sink_ops #define link_ops(csdev) csdev->ops->link_ops +#define helper_ops(csdev) csdev->ops->helper_ops /** * struct coresight_ops_sink - basic operations for a sink @@ -231,10 +240,25 @@ struct coresight_ops_source { struct perf_event *event); }; +/** + * struct coresight_ops_helper - Operations for a helper device. + * + * All operations could pass in a device specific data, which could + * help the helper device to determine what to do. + * + * @enable : Enable the device + * @disable : Disable the device + */ +struct coresight_ops_helper { + int (*enable)(struct coresight_device *csdev, void *data); + int (*disable)(struct coresight_device *csdev, void *data); +}; + struct coresight_ops { const struct coresight_ops_sink *sink_ops; const struct coresight_ops_link *link_ops; const struct coresight_ops_source *source_ops; + const struct coresight_ops_helper *helper_ops; }; #ifdef CONFIG_CORESIGHT -- cgit v1.2.3 From fcacb5c154baaeaee3d89b2a2b7cf6e4ce43f5f5 Mon Sep 17 00:00:00 2001 From: Suzuki K Poulose Date: Wed, 11 Jul 2018 13:40:31 -0600 Subject: coresight: Introduce support for Coresight Address Translation Unit Add the initial support for Coresight Address Translation Unit, which augments the TMC in Coresight SoC-600 by providing an improved Scatter Gather mechanism. CATU is always connected to a single TMC-ETR and converts the AXI address with a translated address (from a given SG table with specific format). The CATU should be programmed in pass through mode and enabled even if the ETR doesn't use the translation by CATU. This patch provides mechanism to enable/disable the CATU always in the pass through mode. We reuse the existing ports mechanism to link the TMC-ETR to the connected CATU. i.e, TMC-ETR:output_port0 -> CATU:input_port0 Reference manual for CATU component is avilable in version r2p0 of : "Arm Coresight System-on-Chip SoC-600 Technical Reference Manual". Cc: Mathieu Poirier Signed-off-by: Suzuki K Poulose Signed-off-by: Mathieu Poirier Signed-off-by: Greg Kroah-Hartman --- drivers/hwtracing/coresight/Kconfig | 11 ++ drivers/hwtracing/coresight/Makefile | 1 + drivers/hwtracing/coresight/coresight-catu.c | 214 ++++++++++++++++++++++++ drivers/hwtracing/coresight/coresight-catu.h | 84 ++++++++++ drivers/hwtracing/coresight/coresight-tmc-etr.c | 52 ++++++ include/linux/coresight.h | 1 + 6 files changed, 363 insertions(+) create mode 100644 drivers/hwtracing/coresight/coresight-catu.c create mode 100644 drivers/hwtracing/coresight/coresight-catu.h diff --git a/drivers/hwtracing/coresight/Kconfig b/drivers/hwtracing/coresight/Kconfig index ef9cb3c164e1..ad34380cac49 100644 --- a/drivers/hwtracing/coresight/Kconfig +++ b/drivers/hwtracing/coresight/Kconfig @@ -31,6 +31,17 @@ config CORESIGHT_LINK_AND_SINK_TMC complies with the generic implementation of the component without special enhancement or added features. +config CORESIGHT_CATU + bool "Coresight Address Translation Unit (CATU) driver" + depends on CORESIGHT_LINK_AND_SINK_TMC + help + Enable support for the Coresight Address Translation Unit (CATU). + CATU supports a scatter gather table of 4K pages, with forward/backward + lookup. CATU helps TMC ETR to use a large physically non-contiguous trace + buffer by translating the addresses used by ETR to the physical address + by looking up the provided table. CATU can also be used in pass-through + mode where the address is not translated. + config CORESIGHT_SINK_TPIU bool "Coresight generic TPIU driver" depends on CORESIGHT_LINKS_AND_SINKS diff --git a/drivers/hwtracing/coresight/Makefile b/drivers/hwtracing/coresight/Makefile index 61db9dd0d571..41870ded51a3 100644 --- a/drivers/hwtracing/coresight/Makefile +++ b/drivers/hwtracing/coresight/Makefile @@ -18,3 +18,4 @@ obj-$(CONFIG_CORESIGHT_SOURCE_ETM4X) += coresight-etm4x.o \ obj-$(CONFIG_CORESIGHT_DYNAMIC_REPLICATOR) += coresight-dynamic-replicator.o obj-$(CONFIG_CORESIGHT_STM) += coresight-stm.o obj-$(CONFIG_CORESIGHT_CPU_DEBUG) += coresight-cpu-debug.o +obj-$(CONFIG_CORESIGHT_CATU) += coresight-catu.o diff --git a/drivers/hwtracing/coresight/coresight-catu.c b/drivers/hwtracing/coresight/coresight-catu.c new file mode 100644 index 000000000000..9d0cb1f6f16b --- /dev/null +++ b/drivers/hwtracing/coresight/coresight-catu.c @@ -0,0 +1,214 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2018 Arm Limited. All rights reserved. + * + * Coresight Address Translation Unit support + * + * Author: Suzuki K Poulose + */ + +#include +#include +#include +#include +#include +#include + +#include "coresight-catu.h" +#include "coresight-priv.h" + +#define csdev_to_catu_drvdata(csdev) \ + dev_get_drvdata(csdev->dev.parent) + +coresight_simple_reg32(struct catu_drvdata, devid, CORESIGHT_DEVID); +coresight_simple_reg32(struct catu_drvdata, control, CATU_CONTROL); +coresight_simple_reg32(struct catu_drvdata, status, CATU_STATUS); +coresight_simple_reg32(struct catu_drvdata, mode, CATU_MODE); +coresight_simple_reg32(struct catu_drvdata, axictrl, CATU_AXICTRL); +coresight_simple_reg32(struct catu_drvdata, irqen, CATU_IRQEN); +coresight_simple_reg64(struct catu_drvdata, sladdr, + CATU_SLADDRLO, CATU_SLADDRHI); +coresight_simple_reg64(struct catu_drvdata, inaddr, + CATU_INADDRLO, CATU_INADDRHI); + +static struct attribute *catu_mgmt_attrs[] = { + &dev_attr_devid.attr, + &dev_attr_control.attr, + &dev_attr_status.attr, + &dev_attr_mode.attr, + &dev_attr_axictrl.attr, + &dev_attr_irqen.attr, + &dev_attr_sladdr.attr, + &dev_attr_inaddr.attr, + NULL, +}; + +static const struct attribute_group catu_mgmt_group = { + .attrs = catu_mgmt_attrs, + .name = "mgmt", +}; + +static const struct attribute_group *catu_groups[] = { + &catu_mgmt_group, + NULL, +}; + + +static inline int catu_wait_for_ready(struct catu_drvdata *drvdata) +{ + return coresight_timeout(drvdata->base, + CATU_STATUS, CATU_STATUS_READY, 1); +} + +static int catu_enable_hw(struct catu_drvdata *drvdata, void *__unused) +{ + u32 control; + + if (catu_wait_for_ready(drvdata)) + dev_warn(drvdata->dev, "Timeout while waiting for READY\n"); + + control = catu_read_control(drvdata); + if (control & BIT(CATU_CONTROL_ENABLE)) { + dev_warn(drvdata->dev, "CATU is already enabled\n"); + return -EBUSY; + } + + control |= BIT(CATU_CONTROL_ENABLE); + catu_write_mode(drvdata, CATU_MODE_PASS_THROUGH); + catu_write_control(drvdata, control); + dev_dbg(drvdata->dev, "Enabled in Pass through mode\n"); + return 0; +} + +static int catu_enable(struct coresight_device *csdev, void *data) +{ + int rc; + struct catu_drvdata *catu_drvdata = csdev_to_catu_drvdata(csdev); + + CS_UNLOCK(catu_drvdata->base); + rc = catu_enable_hw(catu_drvdata, data); + CS_LOCK(catu_drvdata->base); + return rc; +} + +static int catu_disable_hw(struct catu_drvdata *drvdata) +{ + int rc = 0; + + catu_write_control(drvdata, 0); + if (catu_wait_for_ready(drvdata)) { + dev_info(drvdata->dev, "Timeout while waiting for READY\n"); + rc = -EAGAIN; + } + + dev_dbg(drvdata->dev, "Disabled\n"); + return rc; +} + +static int catu_disable(struct coresight_device *csdev, void *__unused) +{ + int rc; + struct catu_drvdata *catu_drvdata = csdev_to_catu_drvdata(csdev); + + CS_UNLOCK(catu_drvdata->base); + rc = catu_disable_hw(catu_drvdata); + CS_LOCK(catu_drvdata->base); + return rc; +} + +const struct coresight_ops_helper catu_helper_ops = { + .enable = catu_enable, + .disable = catu_disable, +}; + +const struct coresight_ops catu_ops = { + .helper_ops = &catu_helper_ops, +}; + +static int catu_probe(struct amba_device *adev, const struct amba_id *id) +{ + int ret = 0; + u32 dma_mask; + struct catu_drvdata *drvdata; + struct coresight_desc catu_desc; + struct coresight_platform_data *pdata = NULL; + struct device *dev = &adev->dev; + struct device_node *np = dev->of_node; + void __iomem *base; + + if (np) { + pdata = of_get_coresight_platform_data(dev, np); + if (IS_ERR(pdata)) { + ret = PTR_ERR(pdata); + goto out; + } + dev->platform_data = pdata; + } + + drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) { + ret = -ENOMEM; + goto out; + } + + drvdata->dev = dev; + dev_set_drvdata(dev, drvdata); + base = devm_ioremap_resource(dev, &adev->res); + if (IS_ERR(base)) { + ret = PTR_ERR(base); + goto out; + } + + /* Setup dma mask for the device */ + dma_mask = readl_relaxed(base + CORESIGHT_DEVID) & 0x3f; + switch (dma_mask) { + case 32: + case 40: + case 44: + case 48: + case 52: + case 56: + case 64: + break; + default: + /* Default to the 40bits as supported by TMC-ETR */ + dma_mask = 40; + } + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(dma_mask)); + if (ret) + goto out; + + drvdata->base = base; + catu_desc.pdata = pdata; + catu_desc.dev = dev; + catu_desc.groups = catu_groups; + catu_desc.type = CORESIGHT_DEV_TYPE_HELPER; + catu_desc.subtype.helper_subtype = CORESIGHT_DEV_SUBTYPE_HELPER_CATU; + catu_desc.ops = &catu_ops; + drvdata->csdev = coresight_register(&catu_desc); + if (IS_ERR(drvdata->csdev)) + ret = PTR_ERR(drvdata->csdev); +out: + pm_runtime_put(&adev->dev); + return ret; +} + +static struct amba_id catu_ids[] = { + { + .id = 0x000bb9ee, + .mask = 0x000fffff, + }, + {}, +}; + +static struct amba_driver catu_driver = { + .drv = { + .name = "coresight-catu", + .owner = THIS_MODULE, + .suppress_bind_attrs = true, + }, + .probe = catu_probe, + .id_table = catu_ids, +}; + +builtin_amba_driver(catu_driver); diff --git a/drivers/hwtracing/coresight/coresight-catu.h b/drivers/hwtracing/coresight/coresight-catu.h new file mode 100644 index 000000000000..4f221fccffca --- /dev/null +++ b/drivers/hwtracing/coresight/coresight-catu.h @@ -0,0 +1,84 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2018 Arm Limited. All rights reserved. + * + * Author: Suzuki K Poulose + */ + +#ifndef _CORESIGHT_CATU_H +#define _CORESIGHT_CATU_H + +#include "coresight-priv.h" + +/* Register offset from base */ +#define CATU_CONTROL 0x000 +#define CATU_MODE 0x004 +#define CATU_AXICTRL 0x008 +#define CATU_IRQEN 0x00c +#define CATU_SLADDRLO 0x020 +#define CATU_SLADDRHI 0x024 +#define CATU_INADDRLO 0x028 +#define CATU_INADDRHI 0x02c +#define CATU_STATUS 0x100 +#define CATU_DEVARCH 0xfbc + +#define CATU_CONTROL_ENABLE 0 + +#define CATU_MODE_PASS_THROUGH 0U +#define CATU_MODE_TRANSLATE 1U + +#define CATU_STATUS_READY 8 +#define CATU_STATUS_ADRERR 0 +#define CATU_STATUS_AXIERR 4 + +#define CATU_IRQEN_ON 0x1 +#define CATU_IRQEN_OFF 0x0 + +struct catu_drvdata { + struct device *dev; + void __iomem *base; + struct coresight_device *csdev; + int irq; +}; + +#define CATU_REG32(name, offset) \ +static inline u32 \ +catu_read_##name(struct catu_drvdata *drvdata) \ +{ \ + return coresight_read_reg_pair(drvdata->base, offset, -1); \ +} \ +static inline void \ +catu_write_##name(struct catu_drvdata *drvdata, u32 val) \ +{ \ + coresight_write_reg_pair(drvdata->base, val, offset, -1); \ +} + +#define CATU_REG_PAIR(name, lo_off, hi_off) \ +static inline u64 \ +catu_read_##name(struct catu_drvdata *drvdata) \ +{ \ + return coresight_read_reg_pair(drvdata->base, lo_off, hi_off); \ +} \ +static inline void \ +catu_write_##name(struct catu_drvdata *drvdata, u64 val) \ +{ \ + coresight_write_reg_pair(drvdata->base, val, lo_off, hi_off); \ +} + +CATU_REG32(control, CATU_CONTROL); +CATU_REG32(mode, CATU_MODE); +CATU_REG_PAIR(sladdr, CATU_SLADDRLO, CATU_SLADDRHI) +CATU_REG_PAIR(inaddr, CATU_INADDRLO, CATU_INADDRHI) + +static inline bool coresight_is_catu_device(struct coresight_device *csdev) +{ + if (!IS_ENABLED(CONFIG_CORESIGHT_CATU)) + return false; + if (csdev->type != CORESIGHT_DEV_TYPE_HELPER) + return false; + if (csdev->subtype.helper_subtype != CORESIGHT_DEV_SUBTYPE_HELPER_CATU) + return false; + return true; +} + +#endif diff --git a/drivers/hwtracing/coresight/coresight-tmc-etr.c b/drivers/hwtracing/coresight/coresight-tmc-etr.c index 8af451230121..e37923acb725 100644 --- a/drivers/hwtracing/coresight/coresight-tmc-etr.c +++ b/drivers/hwtracing/coresight/coresight-tmc-etr.c @@ -9,6 +9,7 @@ #include #include #include +#include "coresight-catu.h" #include "coresight-priv.h" #include "coresight-tmc.h" @@ -701,6 +702,48 @@ static const struct etr_buf_operations etr_sg_buf_ops = { .get_data = tmc_etr_get_data_sg_buf, }; +/* + * TMC ETR could be connected to a CATU device, which can provide address + * translation service. This is represented by the Output port of the TMC + * (ETR) connected to the input port of the CATU. + * + * Returns : coresight_device ptr for the CATU device if a CATU is found. + * : NULL otherwise. + */ +static inline struct coresight_device * +tmc_etr_get_catu_device(struct tmc_drvdata *drvdata) +{ + int i; + struct coresight_device *tmp, *etr = drvdata->csdev; + + if (!IS_ENABLED(CONFIG_CORESIGHT_CATU)) + return NULL; + + for (i = 0; i < etr->nr_outport; i++) { + tmp = etr->conns[i].child_dev; + if (tmp && coresight_is_catu_device(tmp)) + return tmp; + } + + return NULL; +} + +static inline void tmc_etr_enable_catu(struct tmc_drvdata *drvdata) +{ + struct coresight_device *catu = tmc_etr_get_catu_device(drvdata); + + if (catu && helper_ops(catu)->enable) + helper_ops(catu)->enable(catu, NULL); +} + +static inline void tmc_etr_disable_catu(struct tmc_drvdata *drvdata) +{ + struct coresight_device *catu = tmc_etr_get_catu_device(drvdata); + + if (catu && helper_ops(catu)->disable) + helper_ops(catu)->disable(catu, NULL); +} + static const struct etr_buf_operations *etr_buf_ops[] = { [ETR_MODE_FLAT] = &etr_flat_buf_ops, [ETR_MODE_ETR_SG] = &etr_sg_buf_ops, @@ -844,6 +887,12 @@ static void tmc_etr_enable_hw(struct tmc_drvdata *drvdata) u32 axictl, sts; struct etr_buf *etr_buf = drvdata->etr_buf; + /* + * If this ETR is connected to a CATU, enable it before we turn + * this on + */ + tmc_etr_enable_catu(drvdata); + CS_UNLOCK(drvdata->base); /* Wait for TMCSReady bit to be set */ @@ -952,6 +1001,9 @@ static void tmc_etr_disable_hw(struct tmc_drvdata *drvdata) tmc_disable_hw(drvdata); CS_LOCK(drvdata->base); + + /* Disable CATU device if this ETR is connected to one */ + tmc_etr_disable_catu(drvdata); } static int tmc_enable_etr_sink_sysfs(struct coresight_device *csdev) diff --git a/include/linux/coresight.h b/include/linux/coresight.h index 3d40a2b135f1..d828a6efe0b1 100644 --- a/include/linux/coresight.h +++ b/include/linux/coresight.h @@ -65,6 +65,7 @@ enum coresight_dev_subtype_source { enum coresight_dev_subtype_helper { CORESIGHT_DEV_SUBTYPE_HELPER_NONE, + CORESIGHT_DEV_SUBTYPE_HELPER_CATU, }; /** -- cgit v1.2.3 From a59e5ff928d2f909277017029117fd001207fed0 Mon Sep 17 00:00:00 2001 From: Suzuki K Poulose Date: Wed, 11 Jul 2018 13:40:32 -0600 Subject: dts: bindings: Document device tree binding for CATU Document CATU device-tree bindings. CATU augments the TMC-ETR by providing an improved Scatter Gather mechanism for streaming trace data to non-contiguous system RAM pages. Cc: devicetree@vger.kernel.org Cc: frowand.list@gmail.com Cc: Mark Rutland Cc: Mathieu Poirier Reviewed-by: Rob Herring Signed-off-by: Suzuki K Poulose Signed-off-by: Mathieu Poirier Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/arm/coresight.txt | 53 ++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/Documentation/devicetree/bindings/arm/coresight.txt b/Documentation/devicetree/bindings/arm/coresight.txt index 9aa30a156b57..5d1ad09bafb4 100644 --- a/Documentation/devicetree/bindings/arm/coresight.txt +++ b/Documentation/devicetree/bindings/arm/coresight.txt @@ -39,6 +39,8 @@ its hardware characteristcs. - System Trace Macrocell: "arm,coresight-stm", "arm,primecell"; [1] + - Coresight Address Translation Unit (CATU) + "arm,coresight-catu", "arm,primecell"; * reg: physical base address and length of the register set(s) of the component. @@ -90,6 +92,10 @@ its hardware characteristcs. * arm,scatter-gather: boolean. Indicates that the TMC-ETR can safely use the SG mode on this system. +* Optional property for CATU : + * interrupts : Exactly one SPI may be listed for reporting the address + error + Example: 1. Sinks @@ -121,6 +127,35 @@ Example: }; }; + etr@20070000 { + compatible = "arm,coresight-tmc", "arm,primecell"; + reg = <0 0x20070000 0 0x1000>; + + clocks = <&oscclk6a>; + clock-names = "apb_pclk"; + ports { + #address-cells = <1>; + #size-cells = <0>; + + /* input port */ + port@0 { + reg = <0>; + etr_in_port: endpoint { + slave-mode; + remote-endpoint = <&replicator2_out_port0>; + }; + }; + + /* CATU link represented by output port */ + port@1 { + reg = <1>; + etr_out_port: endpoint { + remote-endpoint = <&catu_in_port>; + }; + }; + }; + }; + 2. Links replicator { /* non-configurable replicators don't show up on the @@ -250,5 +285,23 @@ Example: }; }; +5. CATU + + catu@207e0000 { + compatible = "arm,coresight-catu", "arm,primecell"; + reg = <0 0x207e0000 0 0x1000>; + + clocks = <&oscclk6a>; + clock-names = "apb_pclk"; + + interrupts = ; + port { + catu_in_port: endpoint { + slave-mode; + remote-endpoint = <&etr_out_port>; + }; + }; + }; + [1]. There is currently two version of STM: STM32 and STM500. Both have the same HW interface and as such don't need an explicit binding name. -- cgit v1.2.3 From 8ed536b1e2838dad4f495347f0917b1cb6e3604f Mon Sep 17 00:00:00 2001 From: Suzuki K Poulose Date: Wed, 11 Jul 2018 13:40:33 -0600 Subject: coresight: catu: Add support for scatter gather tables This patch adds the support for setting up a SG table for use by the CATU. We reuse the tmc_sg_table to represent the table/data pages, even though the table format is different. Similar to ETR SG table, CATU uses a 4KB page size for data buffers as well as page tables. All table entries are 64bit wide and have the following format: 63 12 1 0 x-----------------------------------x | Address [63-12] | SBZ | V | x-----------------------------------x Where [V] -> 0 - Pointer is invalid 1 - Pointer is Valid CATU uses only first half of the page for data page pointers. i.e, single table page will only have 256 page pointers, addressing upto 1MB of data. The second half of a table page contains only two pointers at the end of the page (i.e, pointers at index 510 and 511), which are used as links to the "Previous" and "Next" page tables respectively. The first table page has an "Invalid" previous pointer and the next pointer entry points to the second page table if there is one. Similarly the last table page has an "Invalid" next pointer to indicate the end of the table chain. Cc: Mathieu Poirier Signed-off-by: Suzuki K Poulose Signed-off-by: Mathieu Poirier Signed-off-by: Greg Kroah-Hartman --- drivers/hwtracing/coresight/coresight-catu.c | 251 +++++++++++++++++++++++++++ 1 file changed, 251 insertions(+) diff --git a/drivers/hwtracing/coresight/coresight-catu.c b/drivers/hwtracing/coresight/coresight-catu.c index 9d0cb1f6f16b..559d45b6bc39 100644 --- a/drivers/hwtracing/coresight/coresight-catu.c +++ b/drivers/hwtracing/coresight/coresight-catu.c @@ -16,10 +16,261 @@ #include "coresight-catu.h" #include "coresight-priv.h" +#include "coresight-tmc.h" #define csdev_to_catu_drvdata(csdev) \ dev_get_drvdata(csdev->dev.parent) +/* Verbose output for CATU table contents */ +#ifdef CATU_DEBUG +#define catu_dbg(x, ...) dev_dbg(x, __VA_ARGS__) +#else +#define catu_dbg(x, ...) do {} while (0) +#endif + +/* + * CATU uses a page size of 4KB for page tables as well as data pages. + * Each 64bit entry in the table has the following format. + * + * 63 12 1 0 + * ------------------------------------ + * | Address [63-12] | SBZ | V| + * ------------------------------------ + * + * Where bit[0] V indicates if the address is valid or not. + * Each 4K table pages have upto 256 data page pointers, taking upto 2K + * size. There are two Link pointers, pointing to the previous and next + * table pages respectively at the end of the 4K page. (i.e, entry 510 + * and 511). + * E.g, a table of two pages could look like : + * + * Table Page 0 Table Page 1 + * SLADDR ===> x------------------x x--> x-----------------x + * INADDR ->| Page 0 | V | | | Page 256 | V | <- INADDR+1M + * |------------------| | |-----------------| + * INADDR+4K ->| Page 1 | V | | | | + * |------------------| | |-----------------| + * | Page 2 | V | | | | + * |------------------| | |-----------------| + * | ... | V | | | ... | + * |------------------| | |-----------------| + * INADDR+1020K| Page 255 | V | | | Page 511 | V | + * SLADDR+2K==>|------------------| | |-----------------| + * | UNUSED | | | | | + * |------------------| | | | + * | UNUSED | | | | | + * |------------------| | | | + * | ... | | | | | + * |------------------| | |-----------------| + * | IGNORED | 0 | | | Table Page 0| 1 | + * |------------------| | |-----------------| + * | Table Page 1| 1 |--x | IGNORED | 0 | + * x------------------x x-----------------x + * SLADDR+4K==> + * + * The base input address (used by the ETR, programmed in INADDR_{LO,HI}) + * must be aligned to 1MB (the size addressable by a single page table). + * The CATU maps INADDR{LO:HI} to the first page in the table pointed + * to by SLADDR{LO:HI} and so on. + * + */ +typedef u64 cate_t; + +#define CATU_PAGE_SHIFT 12 +#define CATU_PAGE_SIZE (1UL << CATU_PAGE_SHIFT) +#define CATU_PAGES_PER_SYSPAGE (PAGE_SIZE / CATU_PAGE_SIZE) + +/* Page pointers are only allocated in the first 2K half */ +#define CATU_PTRS_PER_PAGE ((CATU_PAGE_SIZE >> 1) / sizeof(cate_t)) +#define CATU_PTRS_PER_SYSPAGE (CATU_PAGES_PER_SYSPAGE * CATU_PTRS_PER_PAGE) +#define CATU_LINK_PREV ((CATU_PAGE_SIZE / sizeof(cate_t)) - 2) +#define CATU_LINK_NEXT ((CATU_PAGE_SIZE / sizeof(cate_t)) - 1) + +#define CATU_ADDR_SHIFT 12 +#define CATU_ADDR_MASK ~(((cate_t)1 << CATU_ADDR_SHIFT) - 1) +#define CATU_ENTRY_VALID ((cate_t)0x1) +#define CATU_VALID_ENTRY(addr) \ + (((cate_t)(addr) & CATU_ADDR_MASK) | CATU_ENTRY_VALID) +#define CATU_ENTRY_ADDR(entry) ((cate_t)(entry) & ~((cate_t)CATU_ENTRY_VALID)) + +/* + * catu_get_table : Retrieve the table pointers for the given @offset + * within the buffer. The buffer is wrapped around to a valid offset. + * + * Returns : The CPU virtual address for the beginning of the table + * containing the data page pointer for @offset. If @daddrp is not NULL, + * @daddrp points the DMA address of the beginning of the table. + */ +static inline cate_t *catu_get_table(struct tmc_sg_table *catu_table, + unsigned long offset, + dma_addr_t *daddrp) +{ + unsigned long buf_size = tmc_sg_table_buf_size(catu_table); + unsigned int table_nr, pg_idx, pg_offset; + struct tmc_pages *table_pages = &catu_table->table_pages; + void *ptr; + + /* Make sure offset is within the range */ + offset %= buf_size; + + /* + * Each table can address 1MB and a single kernel page can + * contain "CATU_PAGES_PER_SYSPAGE" CATU tables. + */ + table_nr = offset >> 20; + /* Find the table page where the table_nr lies in */ + pg_idx = table_nr / CATU_PAGES_PER_SYSPAGE; + pg_offset = (table_nr % CATU_PAGES_PER_SYSPAGE) * CATU_PAGE_SIZE; + if (daddrp) + *daddrp = table_pages->daddrs[pg_idx] + pg_offset; + ptr = page_address(table_pages->pages[pg_idx]); + return (cate_t *)((unsigned long)ptr + pg_offset); +} + +#ifdef CATU_DEBUG +static void catu_dump_table(struct tmc_sg_table *catu_table) +{ + int i; + cate_t *table; + unsigned long table_end, buf_size, offset = 0; + + buf_size = tmc_sg_table_buf_size(catu_table); + dev_dbg(catu_table->dev, + "Dump table %p, tdaddr: %llx\n", + catu_table, catu_table->table_daddr); + + while (offset < buf_size) { + table_end = offset + SZ_1M < buf_size ? + offset + SZ_1M : buf_size; + table = catu_get_table(catu_table, offset, NULL); + for (i = 0; offset < table_end; i++, offset += CATU_PAGE_SIZE) + dev_dbg(catu_table->dev, "%d: %llx\n", i, table[i]); + dev_dbg(catu_table->dev, "Prev : %llx, Next: %llx\n", + table[CATU_LINK_PREV], table[CATU_LINK_NEXT]); + dev_dbg(catu_table->dev, "== End of sub-table ==="); + } + dev_dbg(catu_table->dev, "== End of Table ==="); +} + +#else +static inline void catu_dump_table(struct tmc_sg_table *catu_table) +{ +} +#endif + +static inline cate_t catu_make_entry(dma_addr_t addr) +{ + return addr ? CATU_VALID_ENTRY(addr) : 0; +} + +/* + * catu_populate_table : Populate the given CATU table. + * The table is always populated as a circular table. + * i.e, the "prev" link of the "first" table points to the "last" + * table and the "next" link of the "last" table points to the + * "first" table. The buffer should be made linear by calling + * catu_set_table(). + */ +static void +catu_populate_table(struct tmc_sg_table *catu_table) +{ + int i; + int sys_pidx; /* Index to current system data page */ + int catu_pidx; /* Index of CATU page within the system data page */ + unsigned long offset, buf_size, table_end; + dma_addr_t data_daddr; + dma_addr_t prev_taddr, next_taddr, cur_taddr; + cate_t *table_ptr, *next_table; + + buf_size = tmc_sg_table_buf_size(catu_table); + sys_pidx = catu_pidx = 0; + offset = 0; + + table_ptr = catu_get_table(catu_table, 0, &cur_taddr); + prev_taddr = 0; /* Prev link for the first table */ + + while (offset < buf_size) { + /* + * The @offset is always 1M aligned here and we have an + * empty table @table_ptr to fill. Each table can address + * upto 1MB data buffer. The last table may have fewer + * entries if the buffer size is not aligned. + */ + table_end = (offset + SZ_1M) < buf_size ? + (offset + SZ_1M) : buf_size; + for (i = 0; offset < table_end; + i++, offset += CATU_PAGE_SIZE) { + + data_daddr = catu_table->data_pages.daddrs[sys_pidx] + + catu_pidx * CATU_PAGE_SIZE; + catu_dbg(catu_table->dev, + "[table %5ld:%03d] 0x%llx\n", + (offset >> 20), i, data_daddr); + table_ptr[i] = catu_make_entry(data_daddr); + /* Move the pointers for data pages */ + catu_pidx = (catu_pidx + 1) % CATU_PAGES_PER_SYSPAGE; + if (catu_pidx == 0) + sys_pidx++; + } + + /* + * If we have finished all the valid entries, fill the rest of + * the table (i.e, last table page) with invalid entries, + * to fail the lookups. + */ + if (offset == buf_size) { + memset(&table_ptr[i], 0, + sizeof(cate_t) * (CATU_PTRS_PER_PAGE - i)); + next_taddr = 0; + } else { + next_table = catu_get_table(catu_table, + offset, &next_taddr); + } + + table_ptr[CATU_LINK_PREV] = catu_make_entry(prev_taddr); + table_ptr[CATU_LINK_NEXT] = catu_make_entry(next_taddr); + + catu_dbg(catu_table->dev, + "[table%5ld]: Cur: 0x%llx Prev: 0x%llx, Next: 0x%llx\n", + (offset >> 20) - 1, cur_taddr, prev_taddr, next_taddr); + + /* Update the prev/next addresses */ + if (next_taddr) { + prev_taddr = cur_taddr; + cur_taddr = next_taddr; + table_ptr = next_table; + } + } + + /* Sync the table for device */ + tmc_sg_table_sync_table(catu_table); +} + +static struct tmc_sg_table __maybe_unused * +catu_init_sg_table(struct device *catu_dev, int node, + ssize_t size, void **pages) +{ + int nr_tpages; + struct tmc_sg_table *catu_table; + + /* + * Each table can address upto 1MB and we can have + * CATU_PAGES_PER_SYSPAGE tables in a system page. + */ + nr_tpages = DIV_ROUND_UP(size, SZ_1M) / CATU_PAGES_PER_SYSPAGE; + catu_table = tmc_alloc_sg_table(catu_dev, node, nr_tpages, + size >> PAGE_SHIFT, pages); + if (IS_ERR(catu_table)) + return catu_table; + + catu_populate_table(catu_table); + dev_dbg(catu_dev, + "Setup table %p, size %ldKB, %d table pages\n", + catu_table, (unsigned long)size >> 10, nr_tpages); + catu_dump_table(catu_table); + return catu_table; +} + coresight_simple_reg32(struct catu_drvdata, devid, CORESIGHT_DEVID); coresight_simple_reg32(struct catu_drvdata, control, CATU_CONTROL); coresight_simple_reg32(struct catu_drvdata, status, CATU_STATUS); -- cgit v1.2.3 From 434d611cddef1ceed32bf416a363992b01a3ff9a Mon Sep 17 00:00:00 2001 From: Suzuki K Poulose Date: Wed, 11 Jul 2018 13:40:34 -0600 Subject: coresight: catu: Plug in CATU as a backend for ETR buffer Now that we can use a CATU with a scatter gather table, add support for the TMC ETR to make use of the connected CATU in translate mode. This is done by adding CATU as new buffer mode. Cc: Mathieu Poirier Signed-off-by: Suzuki K Poulose Signed-off-by: Mathieu Poirier Signed-off-by: Greg Kroah-Hartman --- drivers/hwtracing/coresight/coresight-catu.c | 122 +++++++++++++++++++++++- drivers/hwtracing/coresight/coresight-catu.h | 35 +++++++ drivers/hwtracing/coresight/coresight-tmc-etr.c | 25 +++-- drivers/hwtracing/coresight/coresight-tmc.h | 3 + 4 files changed, 174 insertions(+), 11 deletions(-) diff --git a/drivers/hwtracing/coresight/coresight-catu.c b/drivers/hwtracing/coresight/coresight-catu.c index 559d45b6bc39..ff94e58845b7 100644 --- a/drivers/hwtracing/coresight/coresight-catu.c +++ b/drivers/hwtracing/coresight/coresight-catu.c @@ -28,6 +28,11 @@ #define catu_dbg(x, ...) do {} while (0) #endif +struct catu_etr_buf { + struct tmc_sg_table *catu_table; + dma_addr_t sladdr; +}; + /* * CATU uses a page size of 4KB for page tables as well as data pages. * Each 64bit entry in the table has the following format. @@ -93,6 +98,9 @@ typedef u64 cate_t; (((cate_t)(addr) & CATU_ADDR_MASK) | CATU_ENTRY_VALID) #define CATU_ENTRY_ADDR(entry) ((cate_t)(entry) & ~((cate_t)CATU_ENTRY_VALID)) +/* CATU expects the INADDR to be aligned to 1M. */ +#define CATU_DEFAULT_INADDR (1ULL << 20) + /* * catu_get_table : Retrieve the table pointers for the given @offset * within the buffer. The buffer is wrapped around to a valid offset. @@ -246,7 +254,7 @@ catu_populate_table(struct tmc_sg_table *catu_table) tmc_sg_table_sync_table(catu_table); } -static struct tmc_sg_table __maybe_unused * +static struct tmc_sg_table * catu_init_sg_table(struct device *catu_dev, int node, ssize_t size, void **pages) { @@ -271,6 +279,91 @@ catu_init_sg_table(struct device *catu_dev, int node, return catu_table; } +static void catu_free_etr_buf(struct etr_buf *etr_buf) +{ + struct catu_etr_buf *catu_buf; + + if (!etr_buf || etr_buf->mode != ETR_MODE_CATU || !etr_buf->private) + return; + + catu_buf = etr_buf->private; + tmc_free_sg_table(catu_buf->catu_table); + kfree(catu_buf); +} + +static ssize_t catu_get_data_etr_buf(struct etr_buf *etr_buf, u64 offset, + size_t len, char **bufpp) +{ + struct catu_etr_buf *catu_buf = etr_buf->private; + + return tmc_sg_table_get_data(catu_buf->catu_table, offset, len, bufpp); +} + +static void catu_sync_etr_buf(struct etr_buf *etr_buf, u64 rrp, u64 rwp) +{ + struct catu_etr_buf *catu_buf = etr_buf->private; + struct tmc_sg_table *catu_table = catu_buf->catu_table; + u64 r_offset, w_offset; + + /* + * ETR started off at etr_buf->hwaddr. Convert the RRP/RWP to + * offsets within the trace buffer. + */ + r_offset = rrp - etr_buf->hwaddr; + w_offset = rwp - etr_buf->hwaddr; + + if (!etr_buf->full) { + etr_buf->len = w_offset - r_offset; + if (w_offset < r_offset) + etr_buf->len += etr_buf->size; + } else { + etr_buf->len = etr_buf->size; + } + + etr_buf->offset = r_offset; + tmc_sg_table_sync_data_range(catu_table, r_offset, etr_buf->len); +} + +static int catu_alloc_etr_buf(struct tmc_drvdata *tmc_drvdata, + struct etr_buf *etr_buf, int node, void **pages) +{ + struct coresight_device *csdev; + struct device *catu_dev; + struct tmc_sg_table *catu_table; + struct catu_etr_buf *catu_buf; + + csdev = tmc_etr_get_catu_device(tmc_drvdata); + if (!csdev) + return -ENODEV; + catu_dev = csdev->dev.parent; + catu_buf = kzalloc(sizeof(*catu_buf), GFP_KERNEL); + if (!catu_buf) + return -ENOMEM; + + catu_table = catu_init_sg_table(catu_dev, node, etr_buf->size, pages); + if (IS_ERR(catu_table)) { + kfree(catu_buf); + return PTR_ERR(catu_table); + } + + etr_buf->mode = ETR_MODE_CATU; + etr_buf->private = catu_buf; + etr_buf->hwaddr = CATU_DEFAULT_INADDR; + + catu_buf->catu_table = catu_table; + /* Get the table base address */ + catu_buf->sladdr = catu_table->table_daddr; + + return 0; +} + +const struct etr_buf_operations etr_catu_buf_ops = { + .alloc = catu_alloc_etr_buf, + .free = catu_free_etr_buf, + .sync = catu_sync_etr_buf, + .get_data = catu_get_data_etr_buf, +}; + coresight_simple_reg32(struct catu_drvdata, devid, CORESIGHT_DEVID); coresight_simple_reg32(struct catu_drvdata, control, CATU_CONTROL); coresight_simple_reg32(struct catu_drvdata, status, CATU_STATUS); @@ -311,9 +404,10 @@ static inline int catu_wait_for_ready(struct catu_drvdata *drvdata) CATU_STATUS, CATU_STATUS_READY, 1); } -static int catu_enable_hw(struct catu_drvdata *drvdata, void *__unused) +static int catu_enable_hw(struct catu_drvdata *drvdata, void *data) { - u32 control; + u32 control, mode; + struct etr_buf *etr_buf = data; if (catu_wait_for_ready(drvdata)) dev_warn(drvdata->dev, "Timeout while waiting for READY\n"); @@ -325,9 +419,27 @@ static int catu_enable_hw(struct catu_drvdata *drvdata, void *__unused) } control |= BIT(CATU_CONTROL_ENABLE); - catu_write_mode(drvdata, CATU_MODE_PASS_THROUGH); + + if (etr_buf && etr_buf->mode == ETR_MODE_CATU) { + struct catu_etr_buf *catu_buf = etr_buf->private; + + mode = CATU_MODE_TRANSLATE; + catu_write_axictrl(drvdata, CATU_OS_AXICTRL); + catu_write_sladdr(drvdata, catu_buf->sladdr); + catu_write_inaddr(drvdata, CATU_DEFAULT_INADDR); + } else { + mode = CATU_MODE_PASS_THROUGH; + catu_write_sladdr(drvdata, 0); + catu_write_inaddr(drvdata, 0); + } + + catu_write_irqen(drvdata, 0); + catu_write_mode(drvdata, mode); catu_write_control(drvdata, control); - dev_dbg(drvdata->dev, "Enabled in Pass through mode\n"); + dev_dbg(drvdata->dev, "Enabled in %s mode\n", + (mode == CATU_MODE_PASS_THROUGH) ? + "Pass through" : + "Translate"); return 0; } diff --git a/drivers/hwtracing/coresight/coresight-catu.h b/drivers/hwtracing/coresight/coresight-catu.h index 4f221fccffca..1b281f0dcccc 100644 --- a/drivers/hwtracing/coresight/coresight-catu.h +++ b/drivers/hwtracing/coresight/coresight-catu.h @@ -27,6 +27,32 @@ #define CATU_MODE_PASS_THROUGH 0U #define CATU_MODE_TRANSLATE 1U +#define CATU_AXICTRL_ARCACHE_SHIFT 4 +#define CATU_AXICTRL_ARCACHE_MASK 0xf +#define CATU_AXICTRL_ARPROT_MASK 0x3 +#define CATU_AXICTRL_ARCACHE(arcache) \ + (((arcache) & CATU_AXICTRL_ARCACHE_MASK) << CATU_AXICTRL_ARCACHE_SHIFT) + +#define CATU_AXICTRL_VAL(arcache, arprot) \ + (CATU_AXICTRL_ARCACHE(arcache) | ((arprot) & CATU_AXICTRL_ARPROT_MASK)) + +#define AXI3_AxCACHE_WB_READ_ALLOC 0x7 +/* + * AXI - ARPROT bits: + * See AMBA AXI & ACE Protocol specification (ARM IHI 0022E) + * sectionA4.7 Access Permissions. + * + * Bit 0: 0 - Unprivileged access, 1 - Privileged access + * Bit 1: 0 - Secure access, 1 - Non-secure access. + * Bit 2: 0 - Data access, 1 - instruction access. + * + * CATU AXICTRL:ARPROT[2] is res0 as we always access data. + */ +#define CATU_OS_ARPROT 0x2 + +#define CATU_OS_AXICTRL \ + CATU_AXICTRL_VAL(AXI3_AxCACHE_WB_READ_ALLOC, CATU_OS_ARPROT) + #define CATU_STATUS_READY 8 #define CATU_STATUS_ADRERR 0 #define CATU_STATUS_AXIERR 4 @@ -67,6 +93,8 @@ catu_write_##name(struct catu_drvdata *drvdata, u64 val) \ CATU_REG32(control, CATU_CONTROL); CATU_REG32(mode, CATU_MODE); +CATU_REG32(irqen, CATU_IRQEN); +CATU_REG32(axictrl, CATU_AXICTRL); CATU_REG_PAIR(sladdr, CATU_SLADDRLO, CATU_SLADDRHI) CATU_REG_PAIR(inaddr, CATU_INADDRLO, CATU_INADDRHI) @@ -81,4 +109,11 @@ static inline bool coresight_is_catu_device(struct coresight_device *csdev) return true; } +#ifdef CONFIG_CORESIGHT_CATU +extern const struct etr_buf_operations etr_catu_buf_ops; +#else +/* Dummy declaration for the CATU ops */ +static const struct etr_buf_operations etr_catu_buf_ops; +#endif + #endif diff --git a/drivers/hwtracing/coresight/coresight-tmc-etr.c b/drivers/hwtracing/coresight/coresight-tmc-etr.c index e37923acb725..2eda5de304c2 100644 --- a/drivers/hwtracing/coresight/coresight-tmc-etr.c +++ b/drivers/hwtracing/coresight/coresight-tmc-etr.c @@ -710,7 +710,7 @@ static const struct etr_buf_operations etr_sg_buf_ops = { * Returns : coresight_device ptr for the CATU device if a CATU is found. * : NULL otherwise. */ -static inline struct coresight_device * +struct coresight_device * tmc_etr_get_catu_device(struct tmc_drvdata *drvdata) { int i; @@ -733,7 +733,7 @@ static inline void tmc_etr_enable_catu(struct tmc_drvdata *drvdata) struct coresight_device *catu = tmc_etr_get_catu_device(drvdata); if (catu && helper_ops(catu)->enable) - helper_ops(catu)->enable(catu, NULL); + helper_ops(catu)->enable(catu, drvdata->etr_buf); } static inline void tmc_etr_disable_catu(struct tmc_drvdata *drvdata) @@ -741,12 +741,13 @@ static inline void tmc_etr_disable_catu(struct tmc_drvdata *drvdata) struct coresight_device *catu = tmc_etr_get_catu_device(drvdata); if (catu && helper_ops(catu)->disable) - helper_ops(catu)->disable(catu, NULL); + helper_ops(catu)->disable(catu, drvdata->etr_buf); } static const struct etr_buf_operations *etr_buf_ops[] = { [ETR_MODE_FLAT] = &etr_flat_buf_ops, [ETR_MODE_ETR_SG] = &etr_sg_buf_ops, + [ETR_MODE_CATU] = &etr_catu_buf_ops, }; static inline int tmc_etr_mode_alloc_buf(int mode, @@ -754,12 +755,15 @@ static inline int tmc_etr_mode_alloc_buf(int mode, struct etr_buf *etr_buf, int node, void **pages) { - int rc; + int rc = -EINVAL; switch (mode) { case ETR_MODE_FLAT: case ETR_MODE_ETR_SG: - rc = etr_buf_ops[mode]->alloc(drvdata, etr_buf, node, pages); + case ETR_MODE_CATU: + if (etr_buf_ops[mode]->alloc) + rc = etr_buf_ops[mode]->alloc(drvdata, etr_buf, + node, pages); if (!rc) etr_buf->ops = etr_buf_ops[mode]; return rc; @@ -782,10 +786,14 @@ static struct etr_buf *tmc_alloc_etr_buf(struct tmc_drvdata *drvdata, { int rc = -ENOMEM; bool has_etr_sg, has_iommu; + bool has_sg, has_catu; struct etr_buf *etr_buf; has_etr_sg = tmc_etr_has_cap(drvdata, TMC_ETR_SG); has_iommu = iommu_get_domain_for_dev(drvdata->dev); + has_catu = !!tmc_etr_get_catu_device(drvdata); + + has_sg = has_catu || has_etr_sg; etr_buf = kzalloc(sizeof(*etr_buf), GFP_KERNEL); if (!etr_buf) @@ -806,17 +814,22 @@ static struct etr_buf *tmc_alloc_etr_buf(struct tmc_drvdata *drvdata, * */ if (!pages && - (!has_etr_sg || has_iommu || size < SZ_1M)) + (!has_sg || has_iommu || size < SZ_1M)) rc = tmc_etr_mode_alloc_buf(ETR_MODE_FLAT, drvdata, etr_buf, node, pages); if (rc && has_etr_sg) rc = tmc_etr_mode_alloc_buf(ETR_MODE_ETR_SG, drvdata, etr_buf, node, pages); + if (rc && has_catu) + rc = tmc_etr_mode_alloc_buf(ETR_MODE_CATU, drvdata, + etr_buf, node, pages); if (rc) { kfree(etr_buf); return ERR_PTR(rc); } + dev_dbg(drvdata->dev, "allocated buffer of size %ldKB in mode %d\n", + (unsigned long)size >> 10, etr_buf->mode); return etr_buf; } diff --git a/drivers/hwtracing/coresight/coresight-tmc.h b/drivers/hwtracing/coresight/coresight-tmc.h index e7456575bfc1..7027bd60c4cc 100644 --- a/drivers/hwtracing/coresight/coresight-tmc.h +++ b/drivers/hwtracing/coresight/coresight-tmc.h @@ -126,6 +126,7 @@ enum tmc_mem_intf_width { enum etr_mode { ETR_MODE_FLAT, /* Uses contiguous flat buffer */ ETR_MODE_ETR_SG, /* Uses in-built TMC ETR SG mechanism */ + ETR_MODE_CATU, /* Use SG mechanism in CATU */ }; struct etr_buf_operations; @@ -303,4 +304,6 @@ tmc_sg_table_buf_size(struct tmc_sg_table *sg_table) return sg_table->data_pages.nr_pages << PAGE_SHIFT; } +struct coresight_device *tmc_etr_get_catu_device(struct tmc_drvdata *drvdata); + #endif -- cgit v1.2.3 From ccff2dfaceaca4517432f5c149594215fe9098cc Mon Sep 17 00:00:00 2001 From: Robin Murphy Date: Wed, 11 Jul 2018 13:40:35 -0600 Subject: coresight: tpiu: Fix disabling timeouts Probing the TPIU driver under UBSan triggers an out-of-bounds shift warning in coresight_timeout(): ... [ 5.677530] UBSAN: Undefined behaviour in drivers/hwtracing/coresight/coresight.c:929:16 [ 5.685542] shift exponent 64 is too large for 64-bit type 'long unsigned int' ... On closer inspection things are exponentially out of whack because we're passing a bitmask where a bit number should be. Amusingly, it seems that both calls will find their expected values by sheer luck and appear to succeed: 1 << FFCR_FON_MAN ends up at bit 64 which whilst undefined evaluates as zero in practice, while 1 << FFSR_FT_STOPPED finds bit 2 (TCPresent) which apparently is usually tied high. Following the examples of other drivers, define separate FOO and FOO_BIT macros for masks vs. indices, and put things right. CC: Robert Walker CC: Mike Leach CC: Mathieu Poirier Fixes: 11595db8e17f ("coresight: Fix disabling of CoreSight TPIU") Signed-off-by: Robin Murphy Signed-off-by: Mathieu Poirier Signed-off-by: Greg Kroah-Hartman --- drivers/hwtracing/coresight/coresight-tpiu.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/hwtracing/coresight/coresight-tpiu.c b/drivers/hwtracing/coresight/coresight-tpiu.c index 01b7457fe8fc..459ef930d98c 100644 --- a/drivers/hwtracing/coresight/coresight-tpiu.c +++ b/drivers/hwtracing/coresight/coresight-tpiu.c @@ -40,8 +40,9 @@ /** register definition **/ /* FFSR - 0x300 */ -#define FFSR_FT_STOPPED BIT(1) +#define FFSR_FT_STOPPED_BIT 1 /* FFCR - 0x304 */ +#define FFCR_FON_MAN_BIT 6 #define FFCR_FON_MAN BIT(6) #define FFCR_STOP_FI BIT(12) @@ -86,9 +87,9 @@ static void tpiu_disable_hw(struct tpiu_drvdata *drvdata) /* Generate manual flush */ writel_relaxed(FFCR_STOP_FI | FFCR_FON_MAN, drvdata->base + TPIU_FFCR); /* Wait for flush to complete */ - coresight_timeout(drvdata->base, TPIU_FFCR, FFCR_FON_MAN, 0); + coresight_timeout(drvdata->base, TPIU_FFCR, FFCR_FON_MAN_BIT, 0); /* Wait for formatter to stop */ - coresight_timeout(drvdata->base, TPIU_FFSR, FFSR_FT_STOPPED, 1); + coresight_timeout(drvdata->base, TPIU_FFSR, FFSR_FT_STOPPED_BIT, 1); CS_LOCK(drvdata->base); } -- cgit v1.2.3 From c73c9ad286ab01bdd23076e6751d463d9ea18f44 Mon Sep 17 00:00:00 2001 From: Wu Hao Date: Sat, 30 Jun 2018 08:53:08 +0800 Subject: docs: fpga: add a document for FPGA Device Feature List (DFL) Framework Overview Add a document for FPGA Device Feature List (DFL) Framework Overview. Signed-off-by: Enno Luebbers Signed-off-by: Xiao Guangrong Signed-off-by: Wu Hao Acked-by: Alan Tull Signed-off-by: Greg Kroah-Hartman --- Documentation/fpga/dfl.txt | 285 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 285 insertions(+) create mode 100644 Documentation/fpga/dfl.txt diff --git a/Documentation/fpga/dfl.txt b/Documentation/fpga/dfl.txt new file mode 100644 index 000000000000..6df4621c3f2a --- /dev/null +++ b/Documentation/fpga/dfl.txt @@ -0,0 +1,285 @@ +=============================================================================== + FPGA Device Feature List (DFL) Framework Overview +------------------------------------------------------------------------------- + Enno Luebbers + Xiao Guangrong + Wu Hao + +The Device Feature List (DFL) FPGA framework (and drivers according to this +this framework) hides the very details of low layer hardwares and provides +unified interfaces to userspace. Applications could use these interfaces to +configure, enumerate, open and access FPGA accelerators on platforms which +implement the DFL in the device memory. Besides this, the DFL framework +enables system level management functions such as FPGA reconfiguration. + + +Device Feature List (DFL) Overview +================================== +Device Feature List (DFL) defines a linked list of feature headers within the +device MMIO space to provide an extensible way of adding features. Software can +walk through these predefined data structures to enumerate FPGA features: +FPGA Interface Unit (FIU), Accelerated Function Unit (AFU) and Private Features, +as illustrated below: + + Header Header Header Header + +----------+ +-->+----------+ +-->+----------+ +-->+----------+ + | Type | | | Type | | | Type | | | Type | + | FIU | | | Private | | | Private | | | Private | + +----------+ | | Feature | | | Feature | | | Feature | + | Next_DFH |--+ +----------+ | +----------+ | +----------+ + +----------+ | Next_DFH |--+ | Next_DFH |--+ | Next_DFH |--> NULL + | ID | +----------+ +----------+ +----------+ + +----------+ | ID | | ID | | ID | + | Next_AFU |--+ +----------+ +----------+ +----------+ + +----------+ | | Feature | | Feature | | Feature | + | Header | | | Register | | Register | | Register | + | Register | | | Set | | Set | | Set | + | Set | | +----------+ +----------+ +----------+ + +----------+ | Header + +-->+----------+ + | Type | + | AFU | + +----------+ + | Next_DFH |--> NULL + +----------+ + | GUID | + +----------+ + | Header | + | Register | + | Set | + +----------+ + +FPGA Interface Unit (FIU) represents a standalone functional unit for the +interface to FPGA, e.g. the FPGA Management Engine (FME) and Port (more +descriptions on FME and Port in later sections). + +Accelerated Function Unit (AFU) represents a FPGA programmable region and +always connects to a FIU (e.g. a Port) as its child as illustrated above. + +Private Features represent sub features of the FIU and AFU. They could be +various function blocks with different IDs, but all private features which +belong to the same FIU or AFU, must be linked to one list via the Next Device +Feature Header (Next_DFH) pointer. + +Each FIU, AFU and Private Feature could implement its own functional registers. +The functional register set for FIU and AFU, is named as Header Register Set, +e.g. FME Header Register Set, and the one for Private Feature, is named as +Feature Register Set, e.g. FME Partial Reconfiguration Feature Register Set. + +This Device Feature List provides a way of linking features together, it's +convenient for software to locate each feature by walking through this list, +and can be implemented in register regions of any FPGA device. + + +FIU - FME (FPGA Management Engine) +================================== +The FPGA Management Engine performs reconfiguration and other infrastructure +functions. Each FPGA device only has one FME. + +User-space applications can acquire exclusive access to the FME using open(), +and release it using close(). + +The following functions are exposed through ioctls: + + Get driver API version (DFL_FPGA_GET_API_VERSION) + Check for extensions (DFL_FPGA_CHECK_EXTENSION) + Program bitstream (DFL_FPGA_FME_PORT_PR) + +More functions are exposed through sysfs +(/sys/class/fpga_region/regionX/dfl-fme.n/): + + Read bitstream ID (bitstream_id) + bitstream_id indicates version of the static FPGA region. + + Read bitstream metadata (bitstream_metadata) + bitstream_metadata includes detailed information of static FPGA region, + e.g. synthesis date and seed. + + Read number of ports (ports_num) + one FPGA device may have more than one port, this sysfs interface indicates + how many ports the FPGA device has. + + +FIU - PORT +========== +A port represents the interface between the static FPGA fabric and a partially +reconfigurable region containing an AFU. It controls the communication from SW +to the accelerator and exposes features such as reset and debug. Each FPGA +device may have more than one port, but always one AFU per port. + + +AFU +=== +An AFU is attached to a port FIU and exposes a fixed length MMIO region to be +used for accelerator-specific control registers. + +User-space applications can acquire exclusive access to an AFU attached to a +port by using open() on the port device node and release it using close(). + +The following functions are exposed through ioctls: + + Get driver API version (DFL_FPGA_GET_API_VERSION) + Check for extensions (DFL_FPGA_CHECK_EXTENSION) + Get port info (DFL_FPGA_PORT_GET_INFO) + Get MMIO region info (DFL_FPGA_PORT_GET_REGION_INFO) + Map DMA buffer (DFL_FPGA_PORT_DMA_MAP) + Unmap DMA buffer (DFL_FPGA_PORT_DMA_UNMAP) + Reset AFU (*DFL_FPGA_PORT_RESET) + +*DFL_FPGA_PORT_RESET: reset the FPGA Port and its AFU. Userspace can do Port +reset at any time, e.g. during DMA or Partial Reconfiguration. But it should +never cause any system level issue, only functional failure (e.g. DMA or PR +operation failure) and be recoverable from the failure. + +User-space applications can also mmap() accelerator MMIO regions. + +More functions are exposed through sysfs: +(/sys/class/fpga_region///): + + Read Accelerator GUID (afu_id) + afu_id indicates which PR bitstream is programmed to this AFU. + + +DFL Framework Overview +====================== + + +----------+ +--------+ +--------+ +--------+ + | FME | | AFU | | AFU | | AFU | + | Module | | Module | | Module | | Module | + +----------+ +--------+ +--------+ +--------+ + +-----------------------+ + | FPGA Container Device | Device Feature List + | (FPGA Base Region) | Framework + +-----------------------+ +-------------------------------------------------------------------- + +----------------------------+ + | FPGA DFL Device Module | + | (e.g. PCIE/Platform Device)| + +----------------------------+ + +------------------------+ + | FPGA Hardware Device | + +------------------------+ + +DFL framework in kernel provides common interfaces to create container device +(FPGA base region), discover feature devices and their private features from the +given Device Feature Lists and create platform devices for feature devices +(e.g. FME, Port and AFU) with related resources under the container device. It +also abstracts operations for the private features and exposes common ops to +feature device drivers. + +The FPGA DFL Device could be different hardwares, e.g. PCIe device, platform +device and etc. Its driver module is always loaded first once the device is +created by the system. This driver plays an infrastructural role in the +driver architecture. It locates the DFLs in the device memory, handles them +and related resources to common interfaces from DFL framework for enumeration. +(Please refer to drivers/fpga/dfl.c for detailed enumeration APIs). + +The FPGA Management Engine (FME) driver is a platform driver which is loaded +automatically after FME platform device creation from the DFL device module. It +provides the key features for FPGA management, including: + + a) Expose static FPGA region information, e.g. version and metadata. + Users can read related information via sysfs interfaces exposed + by FME driver. + + b) Partial Reconfiguration. The FME driver creates FPGA manager, FPGA + bridges and FPGA regions during PR sub feature initialization. Once + it receives a DFL_FPGA_FME_PORT_PR ioctl from user, it invokes the + common interface function from FPGA Region to complete the partial + reconfiguration of the PR bitstream to the given port. + +Similar to the FME driver, the FPGA Accelerated Function Unit (AFU) driver is +probed once the AFU platform device is created. The main function of this module +is to provide an interface for userspace applications to access the individual +accelerators, including basic reset control on port, AFU MMIO region export, dma +buffer mapping service functions. + +After feature platform devices creation, matched platform drivers will be loaded +automatically to handle different functionalities. Please refer to next sections +for detailed information on functional units which have been already implemented +under this DFL framework. + + +Partial Reconfiguration +======================= +As mentioned above, accelerators can be reconfigured through partial +reconfiguration of a PR bitstream file. The PR bitstream file must have been +generated for the exact static FPGA region and targeted reconfigurable region +(port) of the FPGA, otherwise, the reconfiguration operation will fail and +possibly cause system instability. This compatibility can be checked by +comparing the compatibility ID noted in the header of PR bitstream file against +the compat_id exposed by the target FPGA region. This check is usually done by +userspace before calling the reconfiguration IOCTL. + + +Device enumeration +================== +This section introduces how applications enumerate the fpga device from +the sysfs hierarchy under /sys/class/fpga_region. + +In the example below, two DFL based FPGA devices are installed in the host. Each +fpga device has one FME and two ports (AFUs). + +FPGA regions are created under /sys/class/fpga_region/ + + /sys/class/fpga_region/region0 + /sys/class/fpga_region/region1 + /sys/class/fpga_region/region2 + ... + +Application needs to search each regionX folder, if feature device is found, +(e.g. "dfl-port.n" or "dfl-fme.m" is found), then it's the base +fpga region which represents the FPGA device. + +Each base region has one FME and two ports (AFUs) as child devices: + + /sys/class/fpga_region/region0/dfl-fme.0 + /sys/class/fpga_region/region0/dfl-port.0 + /sys/class/fpga_region/region0/dfl-port.1 + ... + + /sys/class/fpga_region/region3/dfl-fme.1 + /sys/class/fpga_region/region3/dfl-port.2 + /sys/class/fpga_region/region3/dfl-port.3 + ... + +In general, the FME/AFU sysfs interfaces are named as follows: + + /sys/class/fpga_region/// + /sys/class/fpga_region/// + +with 'n' consecutively numbering all FMEs and 'm' consecutively numbering all +ports. + +The device nodes used for ioctl() or mmap() can be referenced through: + + /sys/class/fpga_region///dev + /sys/class/fpga_region///dev + + +Add new FIUs support +==================== +It's possible that developers made some new function blocks (FIUs) under this +DFL framework, then new platform device driver needs to be developed for the +new feature dev (FIU) following the same way as existing feature dev drivers +(e.g. FME and Port/AFU platform device driver). Besides that, it requires +modification on DFL framework enumeration code too, for new FIU type detection +and related platform devices creation. + + +Add new private features support +================================ +In some cases, we may need to add some new private features to existing FIUs +(e.g. FME or Port). Developers don't need to touch enumeration code in DFL +framework, as each private feature will be parsed automatically and related +mmio resources can be found under FIU platform device created by DFL framework. +Developer only needs to provide a sub feature driver with matched feature id. +FME Partial Reconfiguration Sub Feature driver (see drivers/fpga/dfl-fme-pr.c) +could be a reference. + + +Open discussion +=============== +FME driver exports one ioctl (DFL_FPGA_FME_PORT_PR) for partial reconfiguration +to user now. In the future, if unified user interfaces for reconfiguration are +added, FME driver should switch to them from ioctl interface. -- cgit v1.2.3 From 571d78bd458a831cf51dff2afa1dda3309bd82b2 Mon Sep 17 00:00:00 2001 From: Wu Hao Date: Sat, 30 Jun 2018 08:53:09 +0800 Subject: fpga: mgr: add region_id to fpga_image_info This patch adds region_id to fpga_image_info data structure, it allows driver to pass region id information to fpga-mgr via fpga_image_info for fpga reconfiguration function. Signed-off-by: Wu Hao Acked-by: Moritz Fischer Acked-by: Alan Tull Signed-off-by: Greg Kroah-Hartman --- include/linux/fpga/fpga-mgr.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/linux/fpga/fpga-mgr.h b/include/linux/fpga/fpga-mgr.h index eec7c2478b0d..3eb6b9d60d65 100644 --- a/include/linux/fpga/fpga-mgr.h +++ b/include/linux/fpga/fpga-mgr.h @@ -77,6 +77,7 @@ enum fpga_mgr_states { * @sgt: scatter/gather table containing FPGA image * @buf: contiguous buffer containing FPGA image * @count: size of buf + * @region_id: id of target region * @dev: device that owns this * @overlay: Device Tree overlay */ @@ -89,6 +90,7 @@ struct fpga_image_info { struct sg_table *sgt; const char *buf; size_t count; + int region_id; struct device *dev; #ifdef CONFIG_OF struct device_node *overlay; -- cgit v1.2.3 From ecb5fbe299dfaad778033259f35bc696fa1fb743 Mon Sep 17 00:00:00 2001 From: Wu Hao Date: Sat, 30 Jun 2018 08:53:10 +0800 Subject: fpga: mgr: add status for fpga-manager This patch adds status sysfs interface for fpga manager, it's a read only interface which allows user to get fpga manager status, including full/partial reconfiguration error and other status information. It adds a status callback to fpga_manager_ops too, allows each fpga_manager driver to define its own method to collect latest status from hardware. The following sysfs file is created: * /sys/class/fpga_manager//status Return status of fpga manager, including reconfiguration errors. Signed-off-by: Wu Hao Acked-by: Alan Tull Acked-by: Moritz Fischer Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-class-fpga-manager | 24 +++++++++++++++++++ drivers/fpga/fpga-mgr.c | 28 ++++++++++++++++++++++ include/linux/fpga/fpga-mgr.h | 9 +++++++ 3 files changed, 61 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-class-fpga-manager b/Documentation/ABI/testing/sysfs-class-fpga-manager index 23056c532fdd..5284fa33d4c5 100644 --- a/Documentation/ABI/testing/sysfs-class-fpga-manager +++ b/Documentation/ABI/testing/sysfs-class-fpga-manager @@ -35,3 +35,27 @@ Description: Read fpga manager state as a string. * write complete = Doing post programming steps * write complete error = Error while doing post programming * operating = FPGA is programmed and operating + +What: /sys/class/fpga_manager//status +Date: June 2018 +KernelVersion: 4.19 +Contact: Wu Hao +Description: Read fpga manager status as a string. + If FPGA programming operation fails, it could be caused by crc + error or incompatible bitstream image. The intent of this + interface is to provide more detailed information for FPGA + programming errors to userspace. This is a list of strings for + the supported status. + + * reconfig operation error - invalid operations detected by + reconfiguration hardware. + e.g. start reconfiguration + with errors not cleared + * reconfig CRC error - CRC error detected by + reconfiguration hardware. + * reconfig incompatible image - reconfiguration image is + incompatible with hardware + * reconfig IP protocol error - protocol errors detected by + reconfiguration hardware + * reconfig fifo overflow error - FIFO overflow detected by + reconfiguration hardware diff --git a/drivers/fpga/fpga-mgr.c b/drivers/fpga/fpga-mgr.c index c1564cf827fe..a41b07e37884 100644 --- a/drivers/fpga/fpga-mgr.c +++ b/drivers/fpga/fpga-mgr.c @@ -406,12 +406,40 @@ static ssize_t state_show(struct device *dev, return sprintf(buf, "%s\n", state_str[mgr->state]); } +static ssize_t status_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fpga_manager *mgr = to_fpga_manager(dev); + u64 status; + int len = 0; + + if (!mgr->mops->status) + return -ENOENT; + + status = mgr->mops->status(mgr); + + if (status & FPGA_MGR_STATUS_OPERATION_ERR) + len += sprintf(buf + len, "reconfig operation error\n"); + if (status & FPGA_MGR_STATUS_CRC_ERR) + len += sprintf(buf + len, "reconfig CRC error\n"); + if (status & FPGA_MGR_STATUS_INCOMPATIBLE_IMAGE_ERR) + len += sprintf(buf + len, "reconfig incompatible image\n"); + if (status & FPGA_MGR_STATUS_IP_PROTOCOL_ERR) + len += sprintf(buf + len, "reconfig IP protocol error\n"); + if (status & FPGA_MGR_STATUS_FIFO_OVERFLOW_ERR) + len += sprintf(buf + len, "reconfig fifo overflow error\n"); + + return len; +} + static DEVICE_ATTR_RO(name); static DEVICE_ATTR_RO(state); +static DEVICE_ATTR_RO(status); static struct attribute *fpga_mgr_attrs[] = { &dev_attr_name.attr, &dev_attr_state.attr, + &dev_attr_status.attr, NULL, }; ATTRIBUTE_GROUPS(fpga_mgr); diff --git a/include/linux/fpga/fpga-mgr.h b/include/linux/fpga/fpga-mgr.h index 3eb6b9d60d65..e249b7250345 100644 --- a/include/linux/fpga/fpga-mgr.h +++ b/include/linux/fpga/fpga-mgr.h @@ -101,6 +101,7 @@ struct fpga_image_info { * struct fpga_manager_ops - ops for low level fpga manager drivers * @initial_header_size: Maximum number of bytes that should be passed into write_init * @state: returns an enum value of the FPGA's state + * @status: returns status of the FPGA, including reconfiguration error code * @write_init: prepare the FPGA to receive confuration data * @write: write count bytes of configuration data to the FPGA * @write_sg: write the scatter list of configuration data to the FPGA @@ -115,6 +116,7 @@ struct fpga_image_info { struct fpga_manager_ops { size_t initial_header_size; enum fpga_mgr_states (*state)(struct fpga_manager *mgr); + u64 (*status)(struct fpga_manager *mgr); int (*write_init)(struct fpga_manager *mgr, struct fpga_image_info *info, const char *buf, size_t count); @@ -126,6 +128,13 @@ struct fpga_manager_ops { const struct attribute_group **groups; }; +/* FPGA manager status: Partial/Full Reconfiguration errors */ +#define FPGA_MGR_STATUS_OPERATION_ERR BIT(0) +#define FPGA_MGR_STATUS_CRC_ERR BIT(1) +#define FPGA_MGR_STATUS_INCOMPATIBLE_IMAGE_ERR BIT(2) +#define FPGA_MGR_STATUS_IP_PROTOCOL_ERR BIT(3) +#define FPGA_MGR_STATUS_FIFO_OVERFLOW_ERR BIT(4) + /** * struct fpga_manager - fpga manager structure * @name: name of low level fpga manager -- cgit v1.2.3 From 99a560bde313892f87ca81db568a829d3d205882 Mon Sep 17 00:00:00 2001 From: Wu Hao Date: Sat, 30 Jun 2018 08:53:11 +0800 Subject: fpga: mgr: add compat_id support This patch introduces compat_id support to fpga manager, it adds a fpga_compat_id pointer to fpga manager data structure to allow fpga manager drivers to save the compatibility id. This compat_id could be used for compatibility checking before doing partial reconfiguration to associated fpga regions. Signed-off-by: Wu Hao Acked-by: Alan Tull Signed-off-by: Greg Kroah-Hartman --- include/linux/fpga/fpga-mgr.h | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/include/linux/fpga/fpga-mgr.h b/include/linux/fpga/fpga-mgr.h index e249b7250345..8942e61f0028 100644 --- a/include/linux/fpga/fpga-mgr.h +++ b/include/linux/fpga/fpga-mgr.h @@ -135,12 +135,24 @@ struct fpga_manager_ops { #define FPGA_MGR_STATUS_IP_PROTOCOL_ERR BIT(3) #define FPGA_MGR_STATUS_FIFO_OVERFLOW_ERR BIT(4) +/** + * struct fpga_compat_id - id for compatibility check + * + * @id_h: high 64bit of the compat_id + * @id_l: low 64bit of the compat_id + */ +struct fpga_compat_id { + u64 id_h; + u64 id_l; +}; + /** * struct fpga_manager - fpga manager structure * @name: name of low level fpga manager * @dev: fpga manager device * @ref_mutex: only allows one reference to fpga manager * @state: state of fpga manager + * @compat_id: FPGA manager id for compatibility check. * @mops: pointer to struct of fpga manager ops * @priv: low level driver private date */ @@ -149,6 +161,7 @@ struct fpga_manager { struct device dev; struct mutex ref_mutex; enum fpga_mgr_states state; + struct fpga_compat_id *compat_id; const struct fpga_manager_ops *mops; void *priv; }; -- cgit v1.2.3 From 41a8b2c56470b7e4e3e2db93324d50bbbf60cdc4 Mon Sep 17 00:00:00 2001 From: Wu Hao Date: Sat, 30 Jun 2018 08:53:12 +0800 Subject: fpga: region: add compat_id support This patch introduces a compat_id pointer member and sysfs interface for each fpga region, similar as compat_id for fpga manager, it allows applications to read the per region compat_id for compatibility checking before other actions on this fpga-region (e.g. PR). Signed-off-by: Wu Hao Acked-by: Alan Tull Acked-by: Moritz Fischer Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-class-fpga-region | 9 +++++++++ drivers/fpga/fpga-region.c | 22 ++++++++++++++++++++++ include/linux/fpga/fpga-region.h | 2 ++ 3 files changed, 33 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-class-fpga-region diff --git a/Documentation/ABI/testing/sysfs-class-fpga-region b/Documentation/ABI/testing/sysfs-class-fpga-region new file mode 100644 index 000000000000..bc7ec644acc9 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-class-fpga-region @@ -0,0 +1,9 @@ +What: /sys/class/fpga_region//compat_id +Date: June 2018 +KernelVersion: 4.19 +Contact: Wu Hao +Description: FPGA region id for compatibility check, e.g. compatibility + of the FPGA reconfiguration hardware and image. This value + is defined or calculated by the layer that is creating the + FPGA region. This interface returns the compat_id value or + just error code -ENOENT in case compat_id is not used. diff --git a/drivers/fpga/fpga-region.c b/drivers/fpga/fpga-region.c index 6d214d75c7be..0d65220d5ec5 100644 --- a/drivers/fpga/fpga-region.c +++ b/drivers/fpga/fpga-region.c @@ -158,6 +158,27 @@ err_put_region: } EXPORT_SYMBOL_GPL(fpga_region_program_fpga); +static ssize_t compat_id_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fpga_region *region = to_fpga_region(dev); + + if (!region->compat_id) + return -ENOENT; + + return sprintf(buf, "%016llx%016llx\n", + (unsigned long long)region->compat_id->id_h, + (unsigned long long)region->compat_id->id_l); +} + +static DEVICE_ATTR_RO(compat_id); + +static struct attribute *fpga_region_attrs[] = { + &dev_attr_compat_id.attr, + NULL, +}; +ATTRIBUTE_GROUPS(fpga_region); + /** * fpga_region_create - alloc and init a struct fpga_region * @dev: device parent @@ -258,6 +279,7 @@ static int __init fpga_region_init(void) if (IS_ERR(fpga_region_class)) return PTR_ERR(fpga_region_class); + fpga_region_class->dev_groups = fpga_region_groups; fpga_region_class->dev_release = fpga_region_dev_release; return 0; diff --git a/include/linux/fpga/fpga-region.h b/include/linux/fpga/fpga-region.h index d7071cddd727..0521b7f577a4 100644 --- a/include/linux/fpga/fpga-region.h +++ b/include/linux/fpga/fpga-region.h @@ -14,6 +14,7 @@ * @bridge_list: list of FPGA bridges specified in region * @mgr: FPGA manager * @info: FPGA image info + * @compat_id: FPGA region id for compatibility check. * @priv: private data * @get_bridges: optional function to get bridges to a list */ @@ -23,6 +24,7 @@ struct fpga_region { struct list_head bridge_list; struct fpga_manager *mgr; struct fpga_image_info *info; + struct fpga_compat_id *compat_id; void *priv; int (*get_bridges)(struct fpga_region *region); }; -- cgit v1.2.3 From 543be3d8c999b30e1e1c05d30c1ea3f2d922340b Mon Sep 17 00:00:00 2001 From: Wu Hao Date: Sat, 30 Jun 2018 08:53:13 +0800 Subject: fpga: add device feature list support Device Feature List (DFL) defines a feature list structure that creates a linked list of feature headers within the MMIO space to provide an extensible way of adding features. This patch introduces a kernel module to provide basic infrastructure to support FPGA devices which implement the Device Feature List. Usually there will be different features and their sub features linked into the DFL. This code provides common APIs for feature enumeration, it creates a container device (FPGA base region), walks through the DFLs and creates platform devices for feature devices (Currently it only supports two different feature devices, FPGA Management Engine (FME) and Port which the Accelerator Function Unit (AFU) connected to). In order to enumerate the DFLs, the common APIs required low level driver to provide necessary enumeration information (e.g. address for each device feature list for given device) and fill it to the dfl_fpga_enum_info data structure. Please refer to below description for APIs added for enumeration. Functions for enumeration information preparation: *dfl_fpga_enum_info_alloc allocate enumeration information data structure. *dfl_fpga_enum_info_add_dfl add a device feature list to dfl_fpga_enum_info data structure. *dfl_fpga_enum_info_free free dfl_fpga_enum_info data structure and related resources. Functions for feature device enumeration: *dfl_fpga_feature_devs_enumerate enumerate feature devices and return container device. *dfl_fpga_feature_devs_remove remove feature devices under given container device. Signed-off-by: Tim Whisonant Signed-off-by: Enno Luebbers Signed-off-by: Shiva Rao Signed-off-by: Christopher Rauer Signed-off-by: Zhang Yi Signed-off-by: Xiao Guangrong Signed-off-by: Wu Hao Acked-by: Alan Tull Signed-off-by: Greg Kroah-Hartman --- drivers/fpga/Kconfig | 16 ++ drivers/fpga/Makefile | 3 + drivers/fpga/dfl.c | 721 ++++++++++++++++++++++++++++++++++++++++++++++++++ drivers/fpga/dfl.h | 279 +++++++++++++++++++ 4 files changed, 1019 insertions(+) create mode 100644 drivers/fpga/dfl.c create mode 100644 drivers/fpga/dfl.h diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig index ee9c5420c47f..4880fd676a19 100644 --- a/drivers/fpga/Kconfig +++ b/drivers/fpga/Kconfig @@ -130,4 +130,20 @@ config OF_FPGA_REGION Support for loading FPGA images by applying a Device Tree overlay. +config FPGA_DFL + tristate "FPGA Device Feature List (DFL) support" + select FPGA_BRIDGE + select FPGA_REGION + help + Device Feature List (DFL) defines a feature list structure that + creates a linked list of feature headers within the MMIO space + to provide an extensible way of adding features for FPGA. + Driver can walk through the feature headers to enumerate feature + devices (e.g. FPGA Management Engine, Port and Accelerator + Function Unit) and their private features for target FPGA devices. + + Select this option to enable common support for Field-Programmable + Gate Array (FPGA) solutions which implement Device Feature List. + It provides enumeration APIs and feature device infrastructure. + endif # FPGA diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile index f9803dad6919..7a7a11739e00 100644 --- a/drivers/fpga/Makefile +++ b/drivers/fpga/Makefile @@ -28,3 +28,6 @@ obj-$(CONFIG_XILINX_PR_DECOUPLER) += xilinx-pr-decoupler.o # High Level Interfaces obj-$(CONFIG_FPGA_REGION) += fpga-region.o obj-$(CONFIG_OF_FPGA_REGION) += of-fpga-region.o + +# FPGA Device Feature List Support +obj-$(CONFIG_FPGA_DFL) += dfl.o diff --git a/drivers/fpga/dfl.c b/drivers/fpga/dfl.c new file mode 100644 index 000000000000..d1dff360ea14 --- /dev/null +++ b/drivers/fpga/dfl.c @@ -0,0 +1,721 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for FPGA Device Feature List (DFL) Support + * + * Copyright (C) 2017-2018 Intel Corporation, Inc. + * + * Authors: + * Kang Luwei + * Zhang Yi + * Wu Hao + * Xiao Guangrong + */ +#include + +#include "dfl.h" + +static DEFINE_MUTEX(dfl_id_mutex); + +/* + * when adding a new feature dev support in DFL framework, it's required to + * add a new item in enum dfl_id_type and provide related information in below + * dfl_devs table which is indexed by dfl_id_type, e.g. name string used for + * platform device creation (define name strings in dfl.h, as they could be + * reused by platform device drivers). + */ +enum dfl_id_type { + FME_ID, /* fme id allocation and mapping */ + PORT_ID, /* port id allocation and mapping */ + DFL_ID_MAX, +}; + +/** + * dfl_dev_info - dfl feature device information. + * @name: name string of the feature platform device. + * @dfh_id: id value in Device Feature Header (DFH) register by DFL spec. + * @id: idr id of the feature dev. + */ +struct dfl_dev_info { + const char *name; + u32 dfh_id; + struct idr id; +}; + +/* it is indexed by dfl_id_type */ +static struct dfl_dev_info dfl_devs[] = { + {.name = DFL_FPGA_FEATURE_DEV_FME, .dfh_id = DFH_ID_FIU_FME}, + {.name = DFL_FPGA_FEATURE_DEV_PORT, .dfh_id = DFH_ID_FIU_PORT}, +}; + +static void dfl_ids_init(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(dfl_devs); i++) + idr_init(&dfl_devs[i].id); +} + +static void dfl_ids_destroy(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(dfl_devs); i++) + idr_destroy(&dfl_devs[i].id); +} + +static int dfl_id_alloc(enum dfl_id_type type, struct device *dev) +{ + int id; + + WARN_ON(type >= DFL_ID_MAX); + mutex_lock(&dfl_id_mutex); + id = idr_alloc(&dfl_devs[type].id, dev, 0, 0, GFP_KERNEL); + mutex_unlock(&dfl_id_mutex); + + return id; +} + +static void dfl_id_free(enum dfl_id_type type, int id) +{ + WARN_ON(type >= DFL_ID_MAX); + mutex_lock(&dfl_id_mutex); + idr_remove(&dfl_devs[type].id, id); + mutex_unlock(&dfl_id_mutex); +} + +static enum dfl_id_type feature_dev_id_type(struct platform_device *pdev) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(dfl_devs); i++) + if (!strcmp(dfl_devs[i].name, pdev->name)) + return i; + + return DFL_ID_MAX; +} + +static enum dfl_id_type dfh_id_to_type(u32 id) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(dfl_devs); i++) + if (dfl_devs[i].dfh_id == id) + return i; + + return DFL_ID_MAX; +} + +/** + * struct build_feature_devs_info - info collected during feature dev build. + * + * @dev: device to enumerate. + * @cdev: the container device for all feature devices. + * @feature_dev: current feature device. + * @ioaddr: header register region address of feature device in enumeration. + * @sub_features: a sub features linked list for feature device in enumeration. + * @feature_num: number of sub features for feature device in enumeration. + */ +struct build_feature_devs_info { + struct device *dev; + struct dfl_fpga_cdev *cdev; + struct platform_device *feature_dev; + void __iomem *ioaddr; + struct list_head sub_features; + int feature_num; +}; + +/** + * struct dfl_feature_info - sub feature info collected during feature dev build + * + * @fid: id of this sub feature. + * @mmio_res: mmio resource of this sub feature. + * @ioaddr: mapped base address of mmio resource. + * @node: node in sub_features linked list. + */ +struct dfl_feature_info { + u64 fid; + struct resource mmio_res; + void __iomem *ioaddr; + struct list_head node; +}; + +static void dfl_fpga_cdev_add_port_dev(struct dfl_fpga_cdev *cdev, + struct platform_device *port) +{ + struct dfl_feature_platform_data *pdata = dev_get_platdata(&port->dev); + + mutex_lock(&cdev->lock); + list_add(&pdata->node, &cdev->port_dev_list); + get_device(&pdata->dev->dev); + mutex_unlock(&cdev->lock); +} + +/* + * register current feature device, it is called when we need to switch to + * another feature parsing or we have parsed all features on given device + * feature list. + */ +static int build_info_commit_dev(struct build_feature_devs_info *binfo) +{ + struct platform_device *fdev = binfo->feature_dev; + struct dfl_feature_platform_data *pdata; + struct dfl_feature_info *finfo, *p; + int ret, index = 0; + + if (!fdev) + return 0; + + /* + * we do not need to care for the memory which is associated with + * the platform device. After calling platform_device_unregister(), + * it will be automatically freed by device's release() callback, + * platform_device_release(). + */ + pdata = kzalloc(dfl_feature_platform_data_size(binfo->feature_num), + GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + pdata->dev = fdev; + pdata->num = binfo->feature_num; + pdata->dfl_cdev = binfo->cdev; + mutex_init(&pdata->lock); + + /* + * the count should be initialized to 0 to make sure + *__fpga_port_enable() following __fpga_port_disable() + * works properly for port device. + * and it should always be 0 for fme device. + */ + WARN_ON(pdata->disable_count); + + fdev->dev.platform_data = pdata; + + /* each sub feature has one MMIO resource */ + fdev->num_resources = binfo->feature_num; + fdev->resource = kcalloc(binfo->feature_num, sizeof(*fdev->resource), + GFP_KERNEL); + if (!fdev->resource) + return -ENOMEM; + + /* fill features and resource information for feature dev */ + list_for_each_entry_safe(finfo, p, &binfo->sub_features, node) { + struct dfl_feature *feature = &pdata->features[index]; + + /* save resource information for each feature */ + feature->id = finfo->fid; + feature->resource_index = index; + feature->ioaddr = finfo->ioaddr; + fdev->resource[index++] = finfo->mmio_res; + + list_del(&finfo->node); + kfree(finfo); + } + + ret = platform_device_add(binfo->feature_dev); + if (!ret) { + if (feature_dev_id_type(binfo->feature_dev) == PORT_ID) + dfl_fpga_cdev_add_port_dev(binfo->cdev, + binfo->feature_dev); + else + binfo->cdev->fme_dev = + get_device(&binfo->feature_dev->dev); + /* + * reset it to avoid build_info_free() freeing their resource. + * + * The resource of successfully registered feature devices + * will be freed by platform_device_unregister(). See the + * comments in build_info_create_dev(). + */ + binfo->feature_dev = NULL; + } + + return ret; +} + +static int +build_info_create_dev(struct build_feature_devs_info *binfo, + enum dfl_id_type type, void __iomem *ioaddr) +{ + struct platform_device *fdev; + int ret; + + if (type >= DFL_ID_MAX) + return -EINVAL; + + /* we will create a new device, commit current device first */ + ret = build_info_commit_dev(binfo); + if (ret) + return ret; + + /* + * we use -ENODEV as the initialization indicator which indicates + * whether the id need to be reclaimed + */ + fdev = platform_device_alloc(dfl_devs[type].name, -ENODEV); + if (!fdev) + return -ENOMEM; + + binfo->feature_dev = fdev; + binfo->feature_num = 0; + binfo->ioaddr = ioaddr; + INIT_LIST_HEAD(&binfo->sub_features); + + fdev->id = dfl_id_alloc(type, &fdev->dev); + if (fdev->id < 0) + return fdev->id; + + fdev->dev.parent = &binfo->cdev->region->dev; + + return 0; +} + +static void build_info_free(struct build_feature_devs_info *binfo) +{ + struct dfl_feature_info *finfo, *p; + + /* + * it is a valid id, free it. See comments in + * build_info_create_dev() + */ + if (binfo->feature_dev && binfo->feature_dev->id >= 0) { + dfl_id_free(feature_dev_id_type(binfo->feature_dev), + binfo->feature_dev->id); + + list_for_each_entry_safe(finfo, p, &binfo->sub_features, node) { + list_del(&finfo->node); + kfree(finfo); + } + } + + platform_device_put(binfo->feature_dev); + + devm_kfree(binfo->dev, binfo); +} + +static inline u32 feature_size(void __iomem *start) +{ + u64 v = readq(start + DFH); + u32 ofst = FIELD_GET(DFH_NEXT_HDR_OFST, v); + /* workaround for private features with invalid size, use 4K instead */ + return ofst ? ofst : 4096; +} + +static u64 feature_id(void __iomem *start) +{ + u64 v = readq(start + DFH); + u16 id = FIELD_GET(DFH_ID, v); + u8 type = FIELD_GET(DFH_TYPE, v); + + if (type == DFH_TYPE_FIU) + return FEATURE_ID_FIU_HEADER; + else if (type == DFH_TYPE_PRIVATE) + return id; + else if (type == DFH_TYPE_AFU) + return FEATURE_ID_AFU; + + WARN_ON(1); + return 0; +} + +/* + * when create sub feature instances, for private features, it doesn't need + * to provide resource size and feature id as they could be read from DFH + * register. For afu sub feature, its register region only contains user + * defined registers, so never trust any information from it, just use the + * resource size information provided by its parent FIU. + */ +static int +create_feature_instance(struct build_feature_devs_info *binfo, + struct dfl_fpga_enum_dfl *dfl, resource_size_t ofst, + resource_size_t size, u64 fid) +{ + struct dfl_feature_info *finfo; + + /* read feature size and id if inputs are invalid */ + size = size ? size : feature_size(dfl->ioaddr + ofst); + fid = fid ? fid : feature_id(dfl->ioaddr + ofst); + + if (dfl->len - ofst < size) + return -EINVAL; + + finfo = kzalloc(sizeof(*finfo), GFP_KERNEL); + if (!finfo) + return -ENOMEM; + + finfo->fid = fid; + finfo->mmio_res.start = dfl->start + ofst; + finfo->mmio_res.end = finfo->mmio_res.start + size - 1; + finfo->mmio_res.flags = IORESOURCE_MEM; + finfo->ioaddr = dfl->ioaddr + ofst; + + list_add_tail(&finfo->node, &binfo->sub_features); + binfo->feature_num++; + + return 0; +} + +static int parse_feature_port_afu(struct build_feature_devs_info *binfo, + struct dfl_fpga_enum_dfl *dfl, + resource_size_t ofst) +{ + u64 v = readq(binfo->ioaddr + PORT_HDR_CAP); + u32 size = FIELD_GET(PORT_CAP_MMIO_SIZE, v) << 10; + + WARN_ON(!size); + + return create_feature_instance(binfo, dfl, ofst, size, FEATURE_ID_AFU); +} + +static int parse_feature_afu(struct build_feature_devs_info *binfo, + struct dfl_fpga_enum_dfl *dfl, + resource_size_t ofst) +{ + if (!binfo->feature_dev) { + dev_err(binfo->dev, "this AFU does not belong to any FIU.\n"); + return -EINVAL; + } + + switch (feature_dev_id_type(binfo->feature_dev)) { + case PORT_ID: + return parse_feature_port_afu(binfo, dfl, ofst); + default: + dev_info(binfo->dev, "AFU belonging to FIU %s is not supported yet.\n", + binfo->feature_dev->name); + } + + return 0; +} + +static int parse_feature_fiu(struct build_feature_devs_info *binfo, + struct dfl_fpga_enum_dfl *dfl, + resource_size_t ofst) +{ + u32 id, offset; + u64 v; + int ret = 0; + + v = readq(dfl->ioaddr + ofst + DFH); + id = FIELD_GET(DFH_ID, v); + + /* create platform device for dfl feature dev */ + ret = build_info_create_dev(binfo, dfh_id_to_type(id), + dfl->ioaddr + ofst); + if (ret) + return ret; + + ret = create_feature_instance(binfo, dfl, ofst, 0, 0); + if (ret) + return ret; + /* + * find and parse FIU's child AFU via its NEXT_AFU register. + * please note that only Port has valid NEXT_AFU pointer per spec. + */ + v = readq(dfl->ioaddr + ofst + NEXT_AFU); + + offset = FIELD_GET(NEXT_AFU_NEXT_DFH_OFST, v); + if (offset) + return parse_feature_afu(binfo, dfl, ofst + offset); + + dev_dbg(binfo->dev, "No AFUs detected on FIU %d\n", id); + + return ret; +} + +static int parse_feature_private(struct build_feature_devs_info *binfo, + struct dfl_fpga_enum_dfl *dfl, + resource_size_t ofst) +{ + if (!binfo->feature_dev) { + dev_err(binfo->dev, "the private feature %llx does not belong to any AFU.\n", + (unsigned long long)feature_id(dfl->ioaddr + ofst)); + return -EINVAL; + } + + return create_feature_instance(binfo, dfl, ofst, 0, 0); +} + +/** + * parse_feature - parse a feature on given device feature list + * + * @binfo: build feature devices information. + * @dfl: device feature list to parse + * @ofst: offset to feature header on this device feature list + */ +static int parse_feature(struct build_feature_devs_info *binfo, + struct dfl_fpga_enum_dfl *dfl, resource_size_t ofst) +{ + u64 v; + u32 type; + + v = readq(dfl->ioaddr + ofst + DFH); + type = FIELD_GET(DFH_TYPE, v); + + switch (type) { + case DFH_TYPE_AFU: + return parse_feature_afu(binfo, dfl, ofst); + case DFH_TYPE_PRIVATE: + return parse_feature_private(binfo, dfl, ofst); + case DFH_TYPE_FIU: + return parse_feature_fiu(binfo, dfl, ofst); + default: + dev_info(binfo->dev, + "Feature Type %x is not supported.\n", type); + } + + return 0; +} + +static int parse_feature_list(struct build_feature_devs_info *binfo, + struct dfl_fpga_enum_dfl *dfl) +{ + void __iomem *start = dfl->ioaddr; + void __iomem *end = dfl->ioaddr + dfl->len; + int ret = 0; + u32 ofst = 0; + u64 v; + + /* walk through the device feature list via DFH's next DFH pointer. */ + for (; start < end; start += ofst) { + if (end - start < DFH_SIZE) { + dev_err(binfo->dev, "The region is too small to contain a feature.\n"); + return -EINVAL; + } + + ret = parse_feature(binfo, dfl, start - dfl->ioaddr); + if (ret) + return ret; + + v = readq(start + DFH); + ofst = FIELD_GET(DFH_NEXT_HDR_OFST, v); + + /* stop parsing if EOL(End of List) is set or offset is 0 */ + if ((v & DFH_EOL) || !ofst) + break; + } + + /* commit current feature device when reach the end of list */ + return build_info_commit_dev(binfo); +} + +struct dfl_fpga_enum_info *dfl_fpga_enum_info_alloc(struct device *dev) +{ + struct dfl_fpga_enum_info *info; + + get_device(dev); + + info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL); + if (!info) { + put_device(dev); + return NULL; + } + + info->dev = dev; + INIT_LIST_HEAD(&info->dfls); + + return info; +} +EXPORT_SYMBOL_GPL(dfl_fpga_enum_info_alloc); + +void dfl_fpga_enum_info_free(struct dfl_fpga_enum_info *info) +{ + struct dfl_fpga_enum_dfl *tmp, *dfl; + struct device *dev; + + if (!info) + return; + + dev = info->dev; + + /* remove all device feature lists in the list. */ + list_for_each_entry_safe(dfl, tmp, &info->dfls, node) { + list_del(&dfl->node); + devm_kfree(dev, dfl); + } + + devm_kfree(dev, info); + put_device(dev); +} +EXPORT_SYMBOL_GPL(dfl_fpga_enum_info_free); + +/** + * dfl_fpga_enum_info_add_dfl - add info of a device feature list to enum info + * + * @info: ptr to dfl_fpga_enum_info + * @start: mmio resource address of the device feature list. + * @len: mmio resource length of the device feature list. + * @ioaddr: mapped mmio resource address of the device feature list. + * + * One FPGA device may have one or more Device Feature Lists (DFLs), use this + * function to add information of each DFL to common data structure for next + * step enumeration. + * + * Return: 0 on success, negative error code otherwise. + */ +int dfl_fpga_enum_info_add_dfl(struct dfl_fpga_enum_info *info, + resource_size_t start, resource_size_t len, + void __iomem *ioaddr) +{ + struct dfl_fpga_enum_dfl *dfl; + + dfl = devm_kzalloc(info->dev, sizeof(*dfl), GFP_KERNEL); + if (!dfl) + return -ENOMEM; + + dfl->start = start; + dfl->len = len; + dfl->ioaddr = ioaddr; + + list_add_tail(&dfl->node, &info->dfls); + + return 0; +} +EXPORT_SYMBOL_GPL(dfl_fpga_enum_info_add_dfl); + +static int remove_feature_dev(struct device *dev, void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + enum dfl_id_type type = feature_dev_id_type(pdev); + int id = pdev->id; + + platform_device_unregister(pdev); + + dfl_id_free(type, id); + + return 0; +} + +static void remove_feature_devs(struct dfl_fpga_cdev *cdev) +{ + device_for_each_child(&cdev->region->dev, NULL, remove_feature_dev); +} + +/** + * dfl_fpga_feature_devs_enumerate - enumerate feature devices + * @info: information for enumeration. + * + * This function creates a container device (base FPGA region), enumerates + * feature devices based on the enumeration info and creates platform devices + * under the container device. + * + * Return: dfl_fpga_cdev struct on success, -errno on failure + */ +struct dfl_fpga_cdev * +dfl_fpga_feature_devs_enumerate(struct dfl_fpga_enum_info *info) +{ + struct build_feature_devs_info *binfo; + struct dfl_fpga_enum_dfl *dfl; + struct dfl_fpga_cdev *cdev; + int ret = 0; + + if (!info->dev) + return ERR_PTR(-ENODEV); + + cdev = devm_kzalloc(info->dev, sizeof(*cdev), GFP_KERNEL); + if (!cdev) + return ERR_PTR(-ENOMEM); + + cdev->region = fpga_region_create(info->dev, NULL, NULL); + if (!cdev->region) { + ret = -ENOMEM; + goto free_cdev_exit; + } + + cdev->parent = info->dev; + mutex_init(&cdev->lock); + INIT_LIST_HEAD(&cdev->port_dev_list); + + ret = fpga_region_register(cdev->region); + if (ret) + goto free_region_exit; + + /* create and init build info for enumeration */ + binfo = devm_kzalloc(info->dev, sizeof(*binfo), GFP_KERNEL); + if (!binfo) { + ret = -ENOMEM; + goto unregister_region_exit; + } + + binfo->dev = info->dev; + binfo->cdev = cdev; + + /* + * start enumeration for all feature devices based on Device Feature + * Lists. + */ + list_for_each_entry(dfl, &info->dfls, node) { + ret = parse_feature_list(binfo, dfl); + if (ret) { + remove_feature_devs(cdev); + build_info_free(binfo); + goto unregister_region_exit; + } + } + + build_info_free(binfo); + + return cdev; + +unregister_region_exit: + fpga_region_unregister(cdev->region); +free_region_exit: + fpga_region_free(cdev->region); +free_cdev_exit: + devm_kfree(info->dev, cdev); + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(dfl_fpga_feature_devs_enumerate); + +/** + * dfl_fpga_feature_devs_remove - remove all feature devices + * @cdev: fpga container device. + * + * Remove the container device and all feature devices under given container + * devices. + */ +void dfl_fpga_feature_devs_remove(struct dfl_fpga_cdev *cdev) +{ + struct dfl_feature_platform_data *pdata, *ptmp; + + remove_feature_devs(cdev); + + mutex_lock(&cdev->lock); + if (cdev->fme_dev) { + /* the fme should be unregistered. */ + WARN_ON(device_is_registered(cdev->fme_dev)); + put_device(cdev->fme_dev); + } + + list_for_each_entry_safe(pdata, ptmp, &cdev->port_dev_list, node) { + struct platform_device *port_dev = pdata->dev; + + /* the port should be unregistered. */ + WARN_ON(device_is_registered(&port_dev->dev)); + list_del(&pdata->node); + put_device(&port_dev->dev); + } + mutex_unlock(&cdev->lock); + + fpga_region_unregister(cdev->region); + devm_kfree(cdev->parent, cdev); +} +EXPORT_SYMBOL_GPL(dfl_fpga_feature_devs_remove); + +static int __init dfl_fpga_init(void) +{ + dfl_ids_init(); + + return 0; +} + +static void __exit dfl_fpga_exit(void) +{ + dfl_ids_destroy(); +} + +module_init(dfl_fpga_init); +module_exit(dfl_fpga_exit); + +MODULE_DESCRIPTION("FPGA Device Feature List (DFL) Support"); +MODULE_AUTHOR("Intel Corporation"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/fpga/dfl.h b/drivers/fpga/dfl.h new file mode 100644 index 000000000000..47ecb3bb6f61 --- /dev/null +++ b/drivers/fpga/dfl.h @@ -0,0 +1,279 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Driver Header File for FPGA Device Feature List (DFL) Support + * + * Copyright (C) 2017-2018 Intel Corporation, Inc. + * + * Authors: + * Kang Luwei + * Zhang Yi + * Wu Hao + * Xiao Guangrong + */ + +#ifndef __FPGA_DFL_H +#define __FPGA_DFL_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* maximum supported number of ports */ +#define MAX_DFL_FPGA_PORT_NUM 4 +/* plus one for fme device */ +#define MAX_DFL_FEATURE_DEV_NUM (MAX_DFL_FPGA_PORT_NUM + 1) + +/* Reserved 0x0 for Header Group Register and 0xff for AFU */ +#define FEATURE_ID_FIU_HEADER 0x0 +#define FEATURE_ID_AFU 0xff + +#define FME_FEATURE_ID_HEADER FEATURE_ID_FIU_HEADER +#define FME_FEATURE_ID_THERMAL_MGMT 0x1 +#define FME_FEATURE_ID_POWER_MGMT 0x2 +#define FME_FEATURE_ID_GLOBAL_IPERF 0x3 +#define FME_FEATURE_ID_GLOBAL_ERR 0x4 +#define FME_FEATURE_ID_PR_MGMT 0x5 +#define FME_FEATURE_ID_HSSI 0x6 +#define FME_FEATURE_ID_GLOBAL_DPERF 0x7 + +#define PORT_FEATURE_ID_HEADER FEATURE_ID_FIU_HEADER +#define PORT_FEATURE_ID_AFU FEATURE_ID_AFU +#define PORT_FEATURE_ID_ERROR 0x10 +#define PORT_FEATURE_ID_UMSG 0x11 +#define PORT_FEATURE_ID_UINT 0x12 +#define PORT_FEATURE_ID_STP 0x13 + +/* + * Device Feature Header Register Set + * + * For FIUs, they all have DFH + GUID + NEXT_AFU as common header registers. + * For AFUs, they have DFH + GUID as common header registers. + * For private features, they only have DFH register as common header. + */ +#define DFH 0x0 +#define GUID_L 0x8 +#define GUID_H 0x10 +#define NEXT_AFU 0x18 + +#define DFH_SIZE 0x8 + +/* Device Feature Header Register Bitfield */ +#define DFH_ID GENMASK_ULL(11, 0) /* Feature ID */ +#define DFH_ID_FIU_FME 0 +#define DFH_ID_FIU_PORT 1 +#define DFH_REVISION GENMASK_ULL(15, 12) /* Feature revision */ +#define DFH_NEXT_HDR_OFST GENMASK_ULL(39, 16) /* Offset to next DFH */ +#define DFH_EOL BIT_ULL(40) /* End of list */ +#define DFH_TYPE GENMASK_ULL(63, 60) /* Feature type */ +#define DFH_TYPE_AFU 1 +#define DFH_TYPE_PRIVATE 3 +#define DFH_TYPE_FIU 4 + +/* Next AFU Register Bitfield */ +#define NEXT_AFU_NEXT_DFH_OFST GENMASK_ULL(23, 0) /* Offset to next AFU */ + +/* FME Header Register Set */ +#define FME_HDR_DFH DFH +#define FME_HDR_GUID_L GUID_L +#define FME_HDR_GUID_H GUID_H +#define FME_HDR_NEXT_AFU NEXT_AFU +#define FME_HDR_CAP 0x30 +#define FME_HDR_PORT_OFST(n) (0x38 + ((n) * 0x8)) +#define FME_HDR_BITSTREAM_ID 0x60 +#define FME_HDR_BITSTREAM_MD 0x68 + +/* FME Fab Capability Register Bitfield */ +#define FME_CAP_FABRIC_VERID GENMASK_ULL(7, 0) /* Fabric version ID */ +#define FME_CAP_SOCKET_ID BIT_ULL(8) /* Socket ID */ +#define FME_CAP_PCIE0_LINK_AVL BIT_ULL(12) /* PCIE0 Link */ +#define FME_CAP_PCIE1_LINK_AVL BIT_ULL(13) /* PCIE1 Link */ +#define FME_CAP_COHR_LINK_AVL BIT_ULL(14) /* Coherent Link */ +#define FME_CAP_IOMMU_AVL BIT_ULL(16) /* IOMMU available */ +#define FME_CAP_NUM_PORTS GENMASK_ULL(19, 17) /* Number of ports */ +#define FME_CAP_ADDR_WIDTH GENMASK_ULL(29, 24) /* Address bus width */ +#define FME_CAP_CACHE_SIZE GENMASK_ULL(43, 32) /* cache size in KB */ +#define FME_CAP_CACHE_ASSOC GENMASK_ULL(47, 44) /* Associativity */ + +/* FME Port Offset Register Bitfield */ +/* Offset to port device feature header */ +#define FME_PORT_OFST_DFH_OFST GENMASK_ULL(23, 0) +/* PCI Bar ID for this port */ +#define FME_PORT_OFST_BAR_ID GENMASK_ULL(34, 32) +/* AFU MMIO access permission. 1 - VF, 0 - PF. */ +#define FME_PORT_OFST_ACC_CTRL BIT_ULL(55) +#define FME_PORT_OFST_ACC_PF 0 +#define FME_PORT_OFST_ACC_VF 1 +#define FME_PORT_OFST_IMP BIT_ULL(60) + +/* PORT Header Register Set */ +#define PORT_HDR_DFH DFH +#define PORT_HDR_GUID_L GUID_L +#define PORT_HDR_GUID_H GUID_H +#define PORT_HDR_NEXT_AFU NEXT_AFU +#define PORT_HDR_CAP 0x30 +#define PORT_HDR_CTRL 0x38 + +/* Port Capability Register Bitfield */ +#define PORT_CAP_PORT_NUM GENMASK_ULL(1, 0) /* ID of this port */ +#define PORT_CAP_MMIO_SIZE GENMASK_ULL(23, 8) /* MMIO size in KB */ +#define PORT_CAP_SUPP_INT_NUM GENMASK_ULL(35, 32) /* Interrupts num */ + +/* Port Control Register Bitfield */ +#define PORT_CTRL_SFTRST BIT_ULL(0) /* Port soft reset */ +/* Latency tolerance reporting. '1' >= 40us, '0' < 40us.*/ +#define PORT_CTRL_LATENCY BIT_ULL(2) +#define PORT_CTRL_SFTRST_ACK BIT_ULL(4) /* HW ack for reset */ + +/** + * struct dfl_feature - sub feature of the feature devices + * + * @id: sub feature id. + * @resource_index: each sub feature has one mmio resource for its registers. + * this index is used to find its mmio resource from the + * feature dev (platform device)'s reources. + * @ioaddr: mapped mmio resource address. + */ +struct dfl_feature { + u64 id; + int resource_index; + void __iomem *ioaddr; +}; + +/** + * struct dfl_feature_platform_data - platform data for feature devices + * + * @node: node to link feature devs to container device's port_dev_list. + * @lock: mutex to protect platform data. + * @dev: ptr to platform device linked with this platform data. + * @dfl_cdev: ptr to container device. + * @disable_count: count for port disable. + * @num: number for sub features. + * @features: sub features of this feature dev. + */ +struct dfl_feature_platform_data { + struct list_head node; + struct mutex lock; + struct platform_device *dev; + struct dfl_fpga_cdev *dfl_cdev; + unsigned int disable_count; + + int num; + struct dfl_feature features[0]; +}; + +#define DFL_FPGA_FEATURE_DEV_FME "dfl-fme" +#define DFL_FPGA_FEATURE_DEV_PORT "dfl-port" + +static inline int dfl_feature_platform_data_size(const int num) +{ + return sizeof(struct dfl_feature_platform_data) + + num * sizeof(struct dfl_feature); +} + +#define dfl_fpga_dev_for_each_feature(pdata, feature) \ + for ((feature) = (pdata)->features; \ + (feature) < (pdata)->features + (pdata)->num; (feature)++) + +static inline +struct dfl_feature *dfl_get_feature_by_id(struct device *dev, u64 id) +{ + struct dfl_feature_platform_data *pdata = dev_get_platdata(dev); + struct dfl_feature *feature; + + dfl_fpga_dev_for_each_feature(pdata, feature) + if (feature->id == id) + return feature; + + return NULL; +} + +static inline +void __iomem *dfl_get_feature_ioaddr_by_id(struct device *dev, u64 id) +{ + struct dfl_feature *feature = dfl_get_feature_by_id(dev, id); + + if (feature && feature->ioaddr) + return feature->ioaddr; + + WARN_ON(1); + return NULL; +} + +static inline bool dfl_feature_is_fme(void __iomem *base) +{ + u64 v = readq(base + DFH); + + return (FIELD_GET(DFH_TYPE, v) == DFH_TYPE_FIU) && + (FIELD_GET(DFH_ID, v) == DFH_ID_FIU_FME); +} + +static inline bool dfl_feature_is_port(void __iomem *base) +{ + u64 v = readq(base + DFH); + + return (FIELD_GET(DFH_TYPE, v) == DFH_TYPE_FIU) && + (FIELD_GET(DFH_ID, v) == DFH_ID_FIU_PORT); +} + +/** + * struct dfl_fpga_enum_info - DFL FPGA enumeration information + * + * @dev: parent device. + * @dfls: list of device feature lists. + */ +struct dfl_fpga_enum_info { + struct device *dev; + struct list_head dfls; +}; + +/** + * struct dfl_fpga_enum_dfl - DFL FPGA enumeration device feature list info + * + * @start: base address of this device feature list. + * @len: size of this device feature list. + * @ioaddr: mapped base address of this device feature list. + * @node: node in list of device feature lists. + */ +struct dfl_fpga_enum_dfl { + resource_size_t start; + resource_size_t len; + + void __iomem *ioaddr; + + struct list_head node; +}; + +struct dfl_fpga_enum_info *dfl_fpga_enum_info_alloc(struct device *dev); +int dfl_fpga_enum_info_add_dfl(struct dfl_fpga_enum_info *info, + resource_size_t start, resource_size_t len, + void __iomem *ioaddr); +void dfl_fpga_enum_info_free(struct dfl_fpga_enum_info *info); + +/** + * struct dfl_fpga_cdev - container device of DFL based FPGA + * + * @parent: parent device of this container device. + * @region: base fpga region. + * @fme_dev: FME feature device under this container device. + * @lock: mutex lock to protect the port device list. + * @port_dev_list: list of all port feature devices under this container device. + */ +struct dfl_fpga_cdev { + struct device *parent; + struct fpga_region *region; + struct device *fme_dev; + struct mutex lock; + struct list_head port_dev_list; +}; + +struct dfl_fpga_cdev * +dfl_fpga_feature_devs_enumerate(struct dfl_fpga_enum_info *info); +void dfl_fpga_feature_devs_remove(struct dfl_fpga_cdev *cdev); + +#endif /* __FPGA_DFL_H */ -- cgit v1.2.3 From b16c5147dc3b1a03405f58f6864b56f29ab7aaf9 Mon Sep 17 00:00:00 2001 From: Wu Hao Date: Sat, 30 Jun 2018 08:53:14 +0800 Subject: fpga: dfl: add chardev support for feature devices For feature devices drivers, both the FPGA Management Engine (FME) and Accelerated Function Unit (AFU) driver need to expose user interfaces via the device file, for example, mmap and ioctls. This patch adds chardev support in the dfl driver for feature devices, FME and AFU. It reserves the chardev regions for FME and AFU and provide interfaces for FME and AFU driver to register their device file operations. Signed-off-by: Tim Whisonant Signed-off-by: Enno Luebbers Signed-off-by: Shiva Rao Signed-off-by: Christopher Rauer Signed-off-by: Zhang Yi Signed-off-by: Xiao Guangrong Signed-off-by: Wu Hao Acked-by: Alan Tull Signed-off-by: Greg Kroah-Hartman --- drivers/fpga/dfl.c | 125 +++++++++++++++++++++++++++++++++++++++++++++++++++-- drivers/fpga/dfl.h | 8 ++++ 2 files changed, 130 insertions(+), 3 deletions(-) diff --git a/drivers/fpga/dfl.c b/drivers/fpga/dfl.c index d1dff360ea14..b56933c64371 100644 --- a/drivers/fpga/dfl.c +++ b/drivers/fpga/dfl.c @@ -22,6 +22,11 @@ static DEFINE_MUTEX(dfl_id_mutex); * dfl_devs table which is indexed by dfl_id_type, e.g. name string used for * platform device creation (define name strings in dfl.h, as they could be * reused by platform device drivers). + * + * if the new feature dev needs chardev support, then it's required to add + * a new item in dfl_chardevs table and configure dfl_devs[i].devt_type as + * index to dfl_chardevs table. If no chardev support just set devt_type + * as one invalid index (DFL_FPGA_DEVT_MAX). */ enum dfl_id_type { FME_ID, /* fme id allocation and mapping */ @@ -29,22 +34,48 @@ enum dfl_id_type { DFL_ID_MAX, }; +enum dfl_fpga_devt_type { + DFL_FPGA_DEVT_FME, + DFL_FPGA_DEVT_PORT, + DFL_FPGA_DEVT_MAX, +}; + /** * dfl_dev_info - dfl feature device information. * @name: name string of the feature platform device. * @dfh_id: id value in Device Feature Header (DFH) register by DFL spec. * @id: idr id of the feature dev. + * @devt_type: index to dfl_chrdevs[]. */ struct dfl_dev_info { const char *name; u32 dfh_id; struct idr id; + enum dfl_fpga_devt_type devt_type; }; /* it is indexed by dfl_id_type */ static struct dfl_dev_info dfl_devs[] = { - {.name = DFL_FPGA_FEATURE_DEV_FME, .dfh_id = DFH_ID_FIU_FME}, - {.name = DFL_FPGA_FEATURE_DEV_PORT, .dfh_id = DFH_ID_FIU_PORT}, + {.name = DFL_FPGA_FEATURE_DEV_FME, .dfh_id = DFH_ID_FIU_FME, + .devt_type = DFL_FPGA_DEVT_FME}, + {.name = DFL_FPGA_FEATURE_DEV_PORT, .dfh_id = DFH_ID_FIU_PORT, + .devt_type = DFL_FPGA_DEVT_PORT}, +}; + +/** + * dfl_chardev_info - chardev information of dfl feature device + * @name: nmae string of the char device. + * @devt: devt of the char device. + */ +struct dfl_chardev_info { + const char *name; + dev_t devt; +}; + +/* indexed by enum dfl_fpga_devt_type */ +static struct dfl_chardev_info dfl_chrdevs[] = { + {.name = DFL_FPGA_FEATURE_DEV_FME}, + {.name = DFL_FPGA_FEATURE_DEV_PORT}, }; static void dfl_ids_init(void) @@ -105,6 +136,86 @@ static enum dfl_id_type dfh_id_to_type(u32 id) return DFL_ID_MAX; } +static void dfl_chardev_uinit(void) +{ + int i; + + for (i = 0; i < DFL_FPGA_DEVT_MAX; i++) + if (MAJOR(dfl_chrdevs[i].devt)) { + unregister_chrdev_region(dfl_chrdevs[i].devt, + MINORMASK); + dfl_chrdevs[i].devt = MKDEV(0, 0); + } +} + +static int dfl_chardev_init(void) +{ + int i, ret; + + for (i = 0; i < DFL_FPGA_DEVT_MAX; i++) { + ret = alloc_chrdev_region(&dfl_chrdevs[i].devt, 0, MINORMASK, + dfl_chrdevs[i].name); + if (ret) + goto exit; + } + + return 0; + +exit: + dfl_chardev_uinit(); + return ret; +} + +static dev_t dfl_get_devt(enum dfl_fpga_devt_type type, int id) +{ + if (type >= DFL_FPGA_DEVT_MAX) + return 0; + + return MKDEV(MAJOR(dfl_chrdevs[type].devt), id); +} + +/** + * dfl_fpga_dev_ops_register - register cdev ops for feature dev + * + * @pdev: feature dev. + * @fops: file operations for feature dev's cdev. + * @owner: owning module/driver. + * + * Return: 0 on success, negative error code otherwise. + */ +int dfl_fpga_dev_ops_register(struct platform_device *pdev, + const struct file_operations *fops, + struct module *owner) +{ + struct dfl_feature_platform_data *pdata = dev_get_platdata(&pdev->dev); + + cdev_init(&pdata->cdev, fops); + pdata->cdev.owner = owner; + + /* + * set parent to the feature device so that its refcount is + * decreased after the last refcount of cdev is gone, that + * makes sure the feature device is valid during device + * file's life-cycle. + */ + pdata->cdev.kobj.parent = &pdev->dev.kobj; + + return cdev_add(&pdata->cdev, pdev->dev.devt, 1); +} +EXPORT_SYMBOL_GPL(dfl_fpga_dev_ops_register); + +/** + * dfl_fpga_dev_ops_unregister - unregister cdev ops for feature dev + * @pdev: feature dev. + */ +void dfl_fpga_dev_ops_unregister(struct platform_device *pdev) +{ + struct dfl_feature_platform_data *pdata = dev_get_platdata(&pdev->dev); + + cdev_del(&pdata->cdev); +} +EXPORT_SYMBOL_GPL(dfl_fpga_dev_ops_unregister); + /** * struct build_feature_devs_info - info collected during feature dev build. * @@ -266,6 +377,7 @@ build_info_create_dev(struct build_feature_devs_info *binfo, return fdev->id; fdev->dev.parent = &binfo->cdev->region->dev; + fdev->dev.devt = dfl_get_devt(dfl_devs[type].devt_type, fdev->id); return 0; } @@ -703,13 +815,20 @@ EXPORT_SYMBOL_GPL(dfl_fpga_feature_devs_remove); static int __init dfl_fpga_init(void) { + int ret; + dfl_ids_init(); - return 0; + ret = dfl_chardev_init(); + if (ret) + dfl_ids_destroy(); + + return ret; } static void __exit dfl_fpga_exit(void) { + dfl_chardev_uinit(); dfl_ids_destroy(); } diff --git a/drivers/fpga/dfl.h b/drivers/fpga/dfl.h index 47ecb3bb6f61..66c2ade5a06b 100644 --- a/drivers/fpga/dfl.h +++ b/drivers/fpga/dfl.h @@ -15,6 +15,7 @@ #define __FPGA_DFL_H #include +#include #include #include #include @@ -150,6 +151,7 @@ struct dfl_feature { * * @node: node to link feature devs to container device's port_dev_list. * @lock: mutex to protect platform data. + * @cdev: cdev of feature dev. * @dev: ptr to platform device linked with this platform data. * @dfl_cdev: ptr to container device. * @disable_count: count for port disable. @@ -159,6 +161,7 @@ struct dfl_feature { struct dfl_feature_platform_data { struct list_head node; struct mutex lock; + struct cdev cdev; struct platform_device *dev; struct dfl_fpga_cdev *dfl_cdev; unsigned int disable_count; @@ -176,6 +179,11 @@ static inline int dfl_feature_platform_data_size(const int num) num * sizeof(struct dfl_feature); } +int dfl_fpga_dev_ops_register(struct platform_device *pdev, + const struct file_operations *fops, + struct module *owner); +void dfl_fpga_dev_ops_unregister(struct platform_device *pdev); + #define dfl_fpga_dev_for_each_feature(pdata, feature) \ for ((feature) = (pdata)->features; \ (feature) < (pdata)->features + (pdata)->num; (feature)++) -- cgit v1.2.3 From 5d56e117001996766c3dab5767663b0c43b76639 Mon Sep 17 00:00:00 2001 From: Wu Hao Date: Sat, 30 Jun 2018 08:53:15 +0800 Subject: fpga: dfl: add dfl_fpga_cdev_find_port For feature devices, we need a method to find the port dedicated to the device. This patch adds a function dfl_fpga_cdev_find_port for this purpose. e.g. FPGA Management Engine (FME) Partial Reconfiguration sub feature, it uses this function to find dedicated port on the device for PR function implementation. Signed-off-by: Tim Whisonant Signed-off-by: Enno Luebbers Signed-off-by: Shiva Rao Signed-off-by: Christopher Rauer Signed-off-by: Xiao Guangrong Signed-off-by: Wu Hao Acked-by: Alan Tull Acked-by: Moritz Fischer Signed-off-by: Greg Kroah-Hartman --- drivers/fpga/dfl.c | 32 ++++++++++++++++++++++++++++++++ drivers/fpga/dfl.h | 21 +++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/drivers/fpga/dfl.c b/drivers/fpga/dfl.c index b56933c64371..68e0b45617b2 100644 --- a/drivers/fpga/dfl.c +++ b/drivers/fpga/dfl.c @@ -813,6 +813,38 @@ void dfl_fpga_feature_devs_remove(struct dfl_fpga_cdev *cdev) } EXPORT_SYMBOL_GPL(dfl_fpga_feature_devs_remove); +/** + * __dfl_fpga_cdev_find_port - find a port under given container device + * + * @cdev: container device + * @data: data passed to match function + * @match: match function used to find specific port from the port device list + * + * Find a port device under container device. This function needs to be + * invoked with lock held. + * + * Return: pointer to port's platform device if successful, NULL otherwise. + * + * NOTE: you will need to drop the device reference with put_device() after use. + */ +struct platform_device * +__dfl_fpga_cdev_find_port(struct dfl_fpga_cdev *cdev, void *data, + int (*match)(struct platform_device *, void *)) +{ + struct dfl_feature_platform_data *pdata; + struct platform_device *port_dev; + + list_for_each_entry(pdata, &cdev->port_dev_list, node) { + port_dev = pdata->dev; + + if (match(port_dev, data) && get_device(&port_dev->dev)) + return port_dev; + } + + return NULL; +} +EXPORT_SYMBOL_GPL(__dfl_fpga_cdev_find_port); + static int __init dfl_fpga_init(void) { int ret; diff --git a/drivers/fpga/dfl.h b/drivers/fpga/dfl.h index 66c2ade5a06b..b4f65250a29c 100644 --- a/drivers/fpga/dfl.h +++ b/drivers/fpga/dfl.h @@ -284,4 +284,25 @@ struct dfl_fpga_cdev * dfl_fpga_feature_devs_enumerate(struct dfl_fpga_enum_info *info); void dfl_fpga_feature_devs_remove(struct dfl_fpga_cdev *cdev); +/* + * need to drop the device reference with put_device() after use port platform + * device returned by __dfl_fpga_cdev_find_port and dfl_fpga_cdev_find_port + * functions. + */ +struct platform_device * +__dfl_fpga_cdev_find_port(struct dfl_fpga_cdev *cdev, void *data, + int (*match)(struct platform_device *, void *)); + +static inline struct platform_device * +dfl_fpga_cdev_find_port(struct dfl_fpga_cdev *cdev, void *data, + int (*match)(struct platform_device *, void *)) +{ + struct platform_device *pdev; + + mutex_lock(&cdev->lock); + pdev = __dfl_fpga_cdev_find_port(cdev, data, match); + mutex_unlock(&cdev->lock); + + return pdev; +} #endif /* __FPGA_DFL_H */ -- cgit v1.2.3 From 5b57d02a2f94bb04c6b36932412f7f3b1bb38518 Mon Sep 17 00:00:00 2001 From: Xiao Guangrong Date: Sat, 30 Jun 2018 08:53:16 +0800 Subject: fpga: dfl: add feature device infrastructure This patch abstracts the common operations of the sub features and defines the feature_ops data structure, including init, uinit and ioctl function pointers. And this patch adds some common helper functions for FME and AFU drivers, e.g. dfl_feature_dev_use_begin/end which are used to ensure exclusive usage of the feature device file. Signed-off-by: Tim Whisonant Signed-off-by: Enno Luebbers Signed-off-by: Shiva Rao Signed-off-by: Christopher Rauer Signed-off-by: Kang Luwei Signed-off-by: Zhang Yi Signed-off-by: Xiao Guangrong Signed-off-by: Wu Hao Acked-by: Alan Tull Signed-off-by: Greg Kroah-Hartman --- drivers/fpga/dfl.c | 71 ++++++++++++++++++++++++++++++++++++++++++++++ drivers/fpga/dfl.h | 82 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 152 insertions(+), 1 deletion(-) diff --git a/drivers/fpga/dfl.c b/drivers/fpga/dfl.c index 68e0b45617b2..e2c72c5dd9e6 100644 --- a/drivers/fpga/dfl.c +++ b/drivers/fpga/dfl.c @@ -136,6 +136,77 @@ static enum dfl_id_type dfh_id_to_type(u32 id) return DFL_ID_MAX; } +/** + * dfl_fpga_dev_feature_uinit - uinit for sub features of dfl feature device + * @pdev: feature device. + */ +void dfl_fpga_dev_feature_uinit(struct platform_device *pdev) +{ + struct dfl_feature_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct dfl_feature *feature; + + dfl_fpga_dev_for_each_feature(pdata, feature) + if (feature->ops) { + feature->ops->uinit(pdev, feature); + feature->ops = NULL; + } +} +EXPORT_SYMBOL_GPL(dfl_fpga_dev_feature_uinit); + +static int dfl_feature_instance_init(struct platform_device *pdev, + struct dfl_feature_platform_data *pdata, + struct dfl_feature *feature, + struct dfl_feature_driver *drv) +{ + int ret; + + ret = drv->ops->init(pdev, feature); + if (ret) + return ret; + + feature->ops = drv->ops; + + return ret; +} + +/** + * dfl_fpga_dev_feature_init - init for sub features of dfl feature device + * @pdev: feature device. + * @feature_drvs: drvs for sub features. + * + * This function will match sub features with given feature drvs list and + * use matched drv to init related sub feature. + * + * Return: 0 on success, negative error code otherwise. + */ +int dfl_fpga_dev_feature_init(struct platform_device *pdev, + struct dfl_feature_driver *feature_drvs) +{ + struct dfl_feature_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct dfl_feature_driver *drv = feature_drvs; + struct dfl_feature *feature; + int ret; + + while (drv->ops) { + dfl_fpga_dev_for_each_feature(pdata, feature) { + /* match feature and drv using id */ + if (feature->id == drv->id) { + ret = dfl_feature_instance_init(pdev, pdata, + feature, drv); + if (ret) + goto exit; + } + } + drv++; + } + + return 0; +exit: + dfl_fpga_dev_feature_uinit(pdev); + return ret; +} +EXPORT_SYMBOL_GPL(dfl_fpga_dev_feature_init); + static void dfl_chardev_uinit(void) { int i; diff --git a/drivers/fpga/dfl.h b/drivers/fpga/dfl.h index b4f65250a29c..14d4731c13b0 100644 --- a/drivers/fpga/dfl.h +++ b/drivers/fpga/dfl.h @@ -131,6 +131,17 @@ #define PORT_CTRL_LATENCY BIT_ULL(2) #define PORT_CTRL_SFTRST_ACK BIT_ULL(4) /* HW ack for reset */ +/** + * struct dfl_feature_driver - sub feature's driver + * + * @id: sub feature id. + * @ops: ops of this sub feature. + */ +struct dfl_feature_driver { + u64 id; + const struct dfl_feature_ops *ops; +}; + /** * struct dfl_feature - sub feature of the feature devices * @@ -139,13 +150,17 @@ * this index is used to find its mmio resource from the * feature dev (platform device)'s reources. * @ioaddr: mapped mmio resource address. + * @ops: ops of this sub feature. */ struct dfl_feature { u64 id; int resource_index; void __iomem *ioaddr; + const struct dfl_feature_ops *ops; }; +#define DEV_STATUS_IN_USE 0 + /** * struct dfl_feature_platform_data - platform data for feature devices * @@ -156,6 +171,8 @@ struct dfl_feature { * @dfl_cdev: ptr to container device. * @disable_count: count for port disable. * @num: number for sub features. + * @dev_status: dev status (e.g. DEV_STATUS_IN_USE). + * @private: ptr to feature dev private data. * @features: sub features of this feature dev. */ struct dfl_feature_platform_data { @@ -165,11 +182,49 @@ struct dfl_feature_platform_data { struct platform_device *dev; struct dfl_fpga_cdev *dfl_cdev; unsigned int disable_count; - + unsigned long dev_status; + void *private; int num; struct dfl_feature features[0]; }; +static inline +int dfl_feature_dev_use_begin(struct dfl_feature_platform_data *pdata) +{ + /* Test and set IN_USE flags to ensure file is exclusively used */ + if (test_and_set_bit_lock(DEV_STATUS_IN_USE, &pdata->dev_status)) + return -EBUSY; + + return 0; +} + +static inline +void dfl_feature_dev_use_end(struct dfl_feature_platform_data *pdata) +{ + clear_bit_unlock(DEV_STATUS_IN_USE, &pdata->dev_status); +} + +static inline +void dfl_fpga_pdata_set_private(struct dfl_feature_platform_data *pdata, + void *private) +{ + pdata->private = private; +} + +static inline +void *dfl_fpga_pdata_get_private(struct dfl_feature_platform_data *pdata) +{ + return pdata->private; +} + +struct dfl_feature_ops { + int (*init)(struct platform_device *pdev, struct dfl_feature *feature); + void (*uinit)(struct platform_device *pdev, + struct dfl_feature *feature); + long (*ioctl)(struct platform_device *pdev, struct dfl_feature *feature, + unsigned int cmd, unsigned long arg); +}; + #define DFL_FPGA_FEATURE_DEV_FME "dfl-fme" #define DFL_FPGA_FEATURE_DEV_PORT "dfl-port" @@ -179,11 +234,25 @@ static inline int dfl_feature_platform_data_size(const int num) num * sizeof(struct dfl_feature); } +void dfl_fpga_dev_feature_uinit(struct platform_device *pdev); +int dfl_fpga_dev_feature_init(struct platform_device *pdev, + struct dfl_feature_driver *feature_drvs); + int dfl_fpga_dev_ops_register(struct platform_device *pdev, const struct file_operations *fops, struct module *owner); void dfl_fpga_dev_ops_unregister(struct platform_device *pdev); +static inline +struct platform_device *dfl_fpga_inode_to_feature_dev(struct inode *inode) +{ + struct dfl_feature_platform_data *pdata; + + pdata = container_of(inode->i_cdev, struct dfl_feature_platform_data, + cdev); + return pdata->dev; +} + #define dfl_fpga_dev_for_each_feature(pdata, feature) \ for ((feature) = (pdata)->features; \ (feature) < (pdata)->features + (pdata)->num; (feature)++) @@ -213,6 +282,17 @@ void __iomem *dfl_get_feature_ioaddr_by_id(struct device *dev, u64 id) return NULL; } +static inline bool is_dfl_feature_present(struct device *dev, u64 id) +{ + return !!dfl_get_feature_ioaddr_by_id(dev, id); +} + +static inline +struct device *dfl_fpga_pdata_to_parent(struct dfl_feature_platform_data *pdata) +{ + return pdata->dev->dev.parent->parent; +} + static inline bool dfl_feature_is_fme(void __iomem *base) { u64 v = readq(base + DFH); -- cgit v1.2.3 From 6e8fd6e493bfca83021cc6a8fd86d7f69bd14fc6 Mon Sep 17 00:00:00 2001 From: Wu Hao Date: Sat, 30 Jun 2018 08:53:17 +0800 Subject: fpga: dfl: add dfl_fpga_port_ops support. In some cases, other DFL driver modules may need to access some port operations, e.g. disable / enable port for partial reconfiguration in FME module. In order to avoid dependency between port and FME modules, this patch introduces the dfl_fpga_port_ops support in DFL framework. A global dfl_fpga_port_ops list is added in the DFL framework, and it allows other DFL modules to use these port operations registered to this list, even in virtualization case, the port platform device is turned into VF / guest VM and hidden in host, the registered port_ops is still usable. It resolves the dependency issues between modules, but once get port ops API returns a valid port ops, that means related port driver module has been module_get to prevent from unexpected unload, and put port ops API must be invoked after use. These APIs introduced by this patch is listed below: * dfl_fpga_port_ops_add add one port ops to the global list. * dfl_fpga_port_ops_del del one port ops from the global list. * dfl_fpga_port_ops_get / dfl_fpga_port_ops_put get/put the port ops before/after use. Signed-off-by: Wu Hao Acked-by: Alan Tull Signed-off-by: Greg Kroah-Hartman --- drivers/fpga/dfl.c | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ drivers/fpga/dfl.h | 21 +++++++++++++++ 2 files changed, 100 insertions(+) diff --git a/drivers/fpga/dfl.c b/drivers/fpga/dfl.c index e2c72c5dd9e6..421668ab613e 100644 --- a/drivers/fpga/dfl.c +++ b/drivers/fpga/dfl.c @@ -136,6 +136,85 @@ static enum dfl_id_type dfh_id_to_type(u32 id) return DFL_ID_MAX; } +/* + * introduce a global port_ops list, it allows port drivers to register ops + * in such list, then other feature devices (e.g. FME), could use the port + * functions even related port platform device is hidden. Below is one example, + * in virtualization case of PCIe-based FPGA DFL device, when SRIOV is + * enabled, port (and it's AFU) is turned into VF and port platform device + * is hidden from system but it's still required to access port to finish FPGA + * reconfiguration function in FME. + */ + +static DEFINE_MUTEX(dfl_port_ops_mutex); +static LIST_HEAD(dfl_port_ops_list); + +/** + * dfl_fpga_port_ops_get - get matched port ops from the global list + * @pdev: platform device to match with associated port ops. + * Return: matched port ops on success, NULL otherwise. + * + * Please note that must dfl_fpga_port_ops_put after use the port_ops. + */ +struct dfl_fpga_port_ops *dfl_fpga_port_ops_get(struct platform_device *pdev) +{ + struct dfl_fpga_port_ops *ops = NULL; + + mutex_lock(&dfl_port_ops_mutex); + if (list_empty(&dfl_port_ops_list)) + goto done; + + list_for_each_entry(ops, &dfl_port_ops_list, node) { + /* match port_ops using the name of platform device */ + if (!strcmp(pdev->name, ops->name)) { + if (!try_module_get(ops->owner)) + ops = NULL; + goto done; + } + } + + ops = NULL; +done: + mutex_unlock(&dfl_port_ops_mutex); + return ops; +} +EXPORT_SYMBOL_GPL(dfl_fpga_port_ops_get); + +/** + * dfl_fpga_port_ops_put - put port ops + * @ops: port ops. + */ +void dfl_fpga_port_ops_put(struct dfl_fpga_port_ops *ops) +{ + if (ops && ops->owner) + module_put(ops->owner); +} +EXPORT_SYMBOL_GPL(dfl_fpga_port_ops_put); + +/** + * dfl_fpga_port_ops_add - add port_ops to global list + * @ops: port ops to add. + */ +void dfl_fpga_port_ops_add(struct dfl_fpga_port_ops *ops) +{ + mutex_lock(&dfl_port_ops_mutex); + list_add_tail(&ops->node, &dfl_port_ops_list); + mutex_unlock(&dfl_port_ops_mutex); +} +EXPORT_SYMBOL_GPL(dfl_fpga_port_ops_add); + +/** + * dfl_fpga_port_ops_del - remove port_ops from global list + * @ops: port ops to del. + */ +void dfl_fpga_port_ops_del(struct dfl_fpga_port_ops *ops) +{ + mutex_lock(&dfl_port_ops_mutex); + list_del(&ops->node); + mutex_unlock(&dfl_port_ops_mutex); +} +EXPORT_SYMBOL_GPL(dfl_fpga_port_ops_del); + /** * dfl_fpga_dev_feature_uinit - uinit for sub features of dfl feature device * @pdev: feature device. diff --git a/drivers/fpga/dfl.h b/drivers/fpga/dfl.h index 14d4731c13b0..654e0f694a5f 100644 --- a/drivers/fpga/dfl.h +++ b/drivers/fpga/dfl.h @@ -130,6 +130,27 @@ /* Latency tolerance reporting. '1' >= 40us, '0' < 40us.*/ #define PORT_CTRL_LATENCY BIT_ULL(2) #define PORT_CTRL_SFTRST_ACK BIT_ULL(4) /* HW ack for reset */ +/** + * struct dfl_fpga_port_ops - port ops + * + * @name: name of this port ops, to match with port platform device. + * @owner: pointer to the module which owns this port ops. + * @node: node to link port ops to global list. + * @get_id: get port id from hardware. + * @enable_set: enable/disable the port. + */ +struct dfl_fpga_port_ops { + const char *name; + struct module *owner; + struct list_head node; + int (*get_id)(struct platform_device *pdev); + int (*enable_set)(struct platform_device *pdev, bool enable); +}; + +void dfl_fpga_port_ops_add(struct dfl_fpga_port_ops *ops); +void dfl_fpga_port_ops_del(struct dfl_fpga_port_ops *ops); +struct dfl_fpga_port_ops *dfl_fpga_port_ops_get(struct platform_device *pdev); +void dfl_fpga_port_ops_put(struct dfl_fpga_port_ops *ops); /** * struct dfl_feature_driver - sub feature's driver -- cgit v1.2.3 From d06b004b99c960828523e581a3b7d109dfc1329b Mon Sep 17 00:00:00 2001 From: Wu Hao Date: Sat, 30 Jun 2018 08:53:18 +0800 Subject: fpga: dfl: add dfl_fpga_check_port_id function. This patch adds one common function in DFL framework. It uses port_ops get_id callback to get port id and compare it with given value. This function could be used as match function of the dfl_fpga_cdev_find_port function. Signed-off-by: Wu Hao Acked-by: Alan Tull Signed-off-by: Greg Kroah-Hartman --- drivers/fpga/dfl.c | 22 ++++++++++++++++++++++ drivers/fpga/dfl.h | 1 + 2 files changed, 23 insertions(+) diff --git a/drivers/fpga/dfl.c b/drivers/fpga/dfl.c index 421668ab613e..a9b521bccb06 100644 --- a/drivers/fpga/dfl.c +++ b/drivers/fpga/dfl.c @@ -215,6 +215,28 @@ void dfl_fpga_port_ops_del(struct dfl_fpga_port_ops *ops) } EXPORT_SYMBOL_GPL(dfl_fpga_port_ops_del); +/** + * dfl_fpga_check_port_id - check the port id + * @pdev: port platform device. + * @pport_id: port id to compare. + * + * Return: 1 if port device matches with given port id, otherwise 0. + */ +int dfl_fpga_check_port_id(struct platform_device *pdev, void *pport_id) +{ + struct dfl_fpga_port_ops *port_ops = dfl_fpga_port_ops_get(pdev); + int port_id; + + if (!port_ops || !port_ops->get_id) + return 0; + + port_id = port_ops->get_id(pdev); + dfl_fpga_port_ops_put(port_ops); + + return port_id == *(int *)pport_id; +} +EXPORT_SYMBOL_GPL(dfl_fpga_check_port_id); + /** * dfl_fpga_dev_feature_uinit - uinit for sub features of dfl feature device * @pdev: feature device. diff --git a/drivers/fpga/dfl.h b/drivers/fpga/dfl.h index 654e0f694a5f..a8b869e9e5b7 100644 --- a/drivers/fpga/dfl.h +++ b/drivers/fpga/dfl.h @@ -151,6 +151,7 @@ void dfl_fpga_port_ops_add(struct dfl_fpga_port_ops *ops); void dfl_fpga_port_ops_del(struct dfl_fpga_port_ops *ops); struct dfl_fpga_port_ops *dfl_fpga_port_ops_get(struct platform_device *pdev); void dfl_fpga_port_ops_put(struct dfl_fpga_port_ops *ops); +int dfl_fpga_check_port_id(struct platform_device *pdev, void *pport_id); /** * struct dfl_feature_driver - sub feature's driver -- cgit v1.2.3 From 72ddd9f34040a49a221c0d5d1754061e007a10e6 Mon Sep 17 00:00:00 2001 From: Zhang Yi Date: Sat, 30 Jun 2018 08:53:19 +0800 Subject: fpga: add FPGA DFL PCIe device driver This patch implements the basic framework of the driver for FPGA PCIe device which implements the Device Feature List (DFL) in its MMIO space. This driver is verified on Intel(R) PCIe-based FPGA DFL devices, including both integrated (e.g. Intel Server Platform with In-package FPGA) and discrete (e.g. Intel FPGA PCIe Acceleration Cards) solutions. Signed-off-by: Tim Whisonant Signed-off-by: Enno Luebbers Signed-off-by: Shiva Rao Signed-off-by: Christopher Rauer Signed-off-by: Zhang Yi Signed-off-by: Xiao Guangrong Signed-off-by: Wu Hao Acked-by: Alan Tull Acked-by: Moritz Fischer Signed-off-by: Greg Kroah-Hartman --- drivers/fpga/Kconfig | 15 +++++++ drivers/fpga/Makefile | 3 ++ drivers/fpga/dfl-pci.c | 103 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 121 insertions(+) create mode 100644 drivers/fpga/dfl-pci.c diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig index 4880fd676a19..f2659f10e92e 100644 --- a/drivers/fpga/Kconfig +++ b/drivers/fpga/Kconfig @@ -146,4 +146,19 @@ config FPGA_DFL Gate Array (FPGA) solutions which implement Device Feature List. It provides enumeration APIs and feature device infrastructure. +config FPGA_DFL_PCI + tristate "FPGA DFL PCIe Device Driver" + depends on PCI && FPGA_DFL + help + Select this option to enable PCIe driver for PCIe-based + Field-Programmable Gate Array (FPGA) solutions which implement + the Device Feature List (DFL). This driver provides interfaces + for userspace applications to configure, enumerate, open and access + FPGA accelerators on the FPGA DFL devices, enables system level + management functions such as FPGA partial reconfiguration, power + management and virtualization with DFL framework and DFL feature + device drivers. + + To compile this as a module, choose M here. + endif # FPGA diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile index 7a7a11739e00..02e025398cd5 100644 --- a/drivers/fpga/Makefile +++ b/drivers/fpga/Makefile @@ -31,3 +31,6 @@ obj-$(CONFIG_OF_FPGA_REGION) += of-fpga-region.o # FPGA Device Feature List Support obj-$(CONFIG_FPGA_DFL) += dfl.o + +# Drivers for FPGAs which implement DFL +obj-$(CONFIG_FPGA_DFL_PCI) += dfl-pci.o diff --git a/drivers/fpga/dfl-pci.c b/drivers/fpga/dfl-pci.c new file mode 100644 index 000000000000..e1e1f0fbe98c --- /dev/null +++ b/drivers/fpga/dfl-pci.c @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for FPGA Device Feature List (DFL) PCIe device + * + * Copyright (C) 2017-2018 Intel Corporation, Inc. + * + * Authors: + * Zhang Yi + * Xiao Guangrong + * Joseph Grecco + * Enno Luebbers + * Tim Whisonant + * Ananda Ravuri + * Henry Mitchel + */ + +#include +#include +#include +#include +#include +#include +#include + +#define DRV_VERSION "0.8" +#define DRV_NAME "dfl-pci" + +/* PCI Device ID */ +#define PCIE_DEVICE_ID_PF_INT_5_X 0xBCBD +#define PCIE_DEVICE_ID_PF_INT_6_X 0xBCC0 +#define PCIE_DEVICE_ID_PF_DSC_1_X 0x09C4 +/* VF Device */ +#define PCIE_DEVICE_ID_VF_INT_5_X 0xBCBF +#define PCIE_DEVICE_ID_VF_INT_6_X 0xBCC1 +#define PCIE_DEVICE_ID_VF_DSC_1_X 0x09C5 + +static struct pci_device_id cci_pcie_id_tbl[] = { + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCIE_DEVICE_ID_PF_INT_5_X),}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCIE_DEVICE_ID_VF_INT_5_X),}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCIE_DEVICE_ID_PF_INT_6_X),}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCIE_DEVICE_ID_VF_INT_6_X),}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCIE_DEVICE_ID_PF_DSC_1_X),}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCIE_DEVICE_ID_VF_DSC_1_X),}, + {0,} +}; +MODULE_DEVICE_TABLE(pci, cci_pcie_id_tbl); + +static +int cci_pci_probe(struct pci_dev *pcidev, const struct pci_device_id *pcidevid) +{ + int ret; + + ret = pcim_enable_device(pcidev); + if (ret < 0) { + dev_err(&pcidev->dev, "Failed to enable device %d.\n", ret); + return ret; + } + + ret = pci_enable_pcie_error_reporting(pcidev); + if (ret && ret != -EINVAL) + dev_info(&pcidev->dev, "PCIE AER unavailable %d.\n", ret); + + pci_set_master(pcidev); + + if (!pci_set_dma_mask(pcidev, DMA_BIT_MASK(64))) { + ret = pci_set_consistent_dma_mask(pcidev, DMA_BIT_MASK(64)); + if (ret) + goto disable_error_report_exit; + } else if (!pci_set_dma_mask(pcidev, DMA_BIT_MASK(32))) { + ret = pci_set_consistent_dma_mask(pcidev, DMA_BIT_MASK(32)); + if (ret) + goto disable_error_report_exit; + } else { + ret = -EIO; + dev_err(&pcidev->dev, "No suitable DMA support available.\n"); + goto disable_error_report_exit; + } + + /* TODO: create and add the platform device per feature list */ + return 0; + +disable_error_report_exit: + pci_disable_pcie_error_reporting(pcidev); + return ret; +} + +static void cci_pci_remove(struct pci_dev *pcidev) +{ + pci_disable_pcie_error_reporting(pcidev); +} + +static struct pci_driver cci_pci_driver = { + .name = DRV_NAME, + .id_table = cci_pcie_id_tbl, + .probe = cci_pci_probe, + .remove = cci_pci_remove, +}; + +module_pci_driver(cci_pci_driver); + +MODULE_DESCRIPTION("FPGA DFL PCIe Device Driver"); +MODULE_AUTHOR("Intel Corporation"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From 968b8199e2585ac4435e2b73af81201e20859c36 Mon Sep 17 00:00:00 2001 From: Wu Hao Date: Sat, 30 Jun 2018 08:53:20 +0800 Subject: fpga: dfl-pci: add enumeration for feature devices The Device Feature List (DFL) is implemented in MMIO and features are linked via the DFLs. This patch enables pcie driver to prepare enumeration information (e.g. locations of all device feature lists in MMIO) and use common APIs provided by the Device Feature List framework to enumerate each feature device linked. Signed-off-by: Tim Whisonant Signed-off-by: Enno Luebbers Signed-off-by: Shiva Rao Signed-off-by: Christopher Rauer Signed-off-by: Zhang Yi Signed-off-by: Xiao Guangrong Signed-off-by: Wu Hao Acked-by: Alan Tull Signed-off-by: Greg Kroah-Hartman --- drivers/fpga/dfl-pci.c | 144 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 142 insertions(+), 2 deletions(-) diff --git a/drivers/fpga/dfl-pci.c b/drivers/fpga/dfl-pci.c index e1e1f0fbe98c..66b5720582bb 100644 --- a/drivers/fpga/dfl-pci.c +++ b/drivers/fpga/dfl-pci.c @@ -22,9 +22,23 @@ #include #include +#include "dfl.h" + #define DRV_VERSION "0.8" #define DRV_NAME "dfl-pci" +struct cci_drvdata { + struct dfl_fpga_cdev *cdev; /* container device */ +}; + +static void __iomem *cci_pci_ioremap_bar(struct pci_dev *pcidev, int bar) +{ + if (pcim_iomap_regions(pcidev, BIT(bar), DRV_NAME)) + return NULL; + + return pcim_iomap_table(pcidev)[bar]; +} + /* PCI Device ID */ #define PCIE_DEVICE_ID_PF_INT_5_X 0xBCBD #define PCIE_DEVICE_ID_PF_INT_6_X 0xBCC0 @@ -45,6 +59,120 @@ static struct pci_device_id cci_pcie_id_tbl[] = { }; MODULE_DEVICE_TABLE(pci, cci_pcie_id_tbl); +static int cci_init_drvdata(struct pci_dev *pcidev) +{ + struct cci_drvdata *drvdata; + + drvdata = devm_kzalloc(&pcidev->dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + pci_set_drvdata(pcidev, drvdata); + + return 0; +} + +static void cci_remove_feature_devs(struct pci_dev *pcidev) +{ + struct cci_drvdata *drvdata = pci_get_drvdata(pcidev); + + /* remove all children feature devices */ + dfl_fpga_feature_devs_remove(drvdata->cdev); +} + +/* enumerate feature devices under pci device */ +static int cci_enumerate_feature_devs(struct pci_dev *pcidev) +{ + struct cci_drvdata *drvdata = pci_get_drvdata(pcidev); + struct dfl_fpga_enum_info *info; + struct dfl_fpga_cdev *cdev; + resource_size_t start, len; + int port_num, bar, i, ret = 0; + void __iomem *base; + u32 offset; + u64 v; + + /* allocate enumeration info via pci_dev */ + info = dfl_fpga_enum_info_alloc(&pcidev->dev); + if (!info) + return -ENOMEM; + + /* start to find Device Feature List from Bar 0 */ + base = cci_pci_ioremap_bar(pcidev, 0); + if (!base) { + ret = -ENOMEM; + goto enum_info_free_exit; + } + + /* + * PF device has FME and Ports/AFUs, and VF device only has one + * Port/AFU. Check them and add related "Device Feature List" info + * for the next step enumeration. + */ + if (dfl_feature_is_fme(base)) { + start = pci_resource_start(pcidev, 0); + len = pci_resource_len(pcidev, 0); + + dfl_fpga_enum_info_add_dfl(info, start, len, base); + + /* + * find more Device Feature Lists (e.g. Ports) per information + * indicated by FME module. + */ + v = readq(base + FME_HDR_CAP); + port_num = FIELD_GET(FME_CAP_NUM_PORTS, v); + + WARN_ON(port_num > MAX_DFL_FPGA_PORT_NUM); + + for (i = 0; i < port_num; i++) { + v = readq(base + FME_HDR_PORT_OFST(i)); + + /* skip ports which are not implemented. */ + if (!(v & FME_PORT_OFST_IMP)) + continue; + + /* + * add Port's Device Feature List information for next + * step enumeration. + */ + bar = FIELD_GET(FME_PORT_OFST_BAR_ID, v); + offset = FIELD_GET(FME_PORT_OFST_DFH_OFST, v); + base = cci_pci_ioremap_bar(pcidev, bar); + if (!base) + continue; + + start = pci_resource_start(pcidev, bar) + offset; + len = pci_resource_len(pcidev, bar) - offset; + + dfl_fpga_enum_info_add_dfl(info, start, len, + base + offset); + } + } else if (dfl_feature_is_port(base)) { + start = pci_resource_start(pcidev, 0); + len = pci_resource_len(pcidev, 0); + + dfl_fpga_enum_info_add_dfl(info, start, len, base); + } else { + ret = -ENODEV; + goto enum_info_free_exit; + } + + /* start enumeration with prepared enumeration information */ + cdev = dfl_fpga_feature_devs_enumerate(info); + if (IS_ERR(cdev)) { + dev_err(&pcidev->dev, "Enumeration failure\n"); + ret = PTR_ERR(cdev); + goto enum_info_free_exit; + } + + drvdata->cdev = cdev; + +enum_info_free_exit: + dfl_fpga_enum_info_free(info); + + return ret; +} + static int cci_pci_probe(struct pci_dev *pcidev, const struct pci_device_id *pcidevid) { @@ -76,8 +204,19 @@ int cci_pci_probe(struct pci_dev *pcidev, const struct pci_device_id *pcidevid) goto disable_error_report_exit; } - /* TODO: create and add the platform device per feature list */ - return 0; + ret = cci_init_drvdata(pcidev); + if (ret) { + dev_err(&pcidev->dev, "Fail to init drvdata %d.\n", ret); + goto disable_error_report_exit; + } + + ret = cci_enumerate_feature_devs(pcidev); + if (ret) { + dev_err(&pcidev->dev, "enumeration failure %d.\n", ret); + goto disable_error_report_exit; + } + + return ret; disable_error_report_exit: pci_disable_pcie_error_reporting(pcidev); @@ -86,6 +225,7 @@ disable_error_report_exit: static void cci_pci_remove(struct pci_dev *pcidev) { + cci_remove_feature_devs(pcidev); pci_disable_pcie_error_reporting(pcidev); } -- cgit v1.2.3 From 322ddebe54ae2b18c86a3bffb2b76bc5e67762ac Mon Sep 17 00:00:00 2001 From: Kang Luwei Date: Sat, 30 Jun 2018 08:53:21 +0800 Subject: fpga: dfl: add FPGA Management Engine driver basic framework The FPGA Management Engine (FME) provides power, thermal management, performance counters, partial reconfiguration and other functions. For each function, it is packaged into a private feature linked to the FME feature device in the 'Device Feature List'. It's a platform device created by DFL framework. This patch adds the basic framework of FME platform driver. It defines sub feature drivers to handle the different sub features, including init, uinit and ioctl. It also registers the file operations for the device file. Signed-off-by: Tim Whisonant Signed-off-by: Enno Luebbers Signed-off-by: Shiva Rao Signed-off-by: Christopher Rauer Signed-off-by: Kang Luwei Signed-off-by: Xiao Guangrong Signed-off-by: Wu Hao Acked-by: Alan Tull Acked-by: Moritz Fischer Signed-off-by: Greg Kroah-Hartman --- drivers/fpga/Kconfig | 10 +++ drivers/fpga/Makefile | 3 + drivers/fpga/dfl-fme-main.c | 158 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 171 insertions(+) create mode 100644 drivers/fpga/dfl-fme-main.c diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig index f2659f10e92e..43803a10cdae 100644 --- a/drivers/fpga/Kconfig +++ b/drivers/fpga/Kconfig @@ -146,6 +146,16 @@ config FPGA_DFL Gate Array (FPGA) solutions which implement Device Feature List. It provides enumeration APIs and feature device infrastructure. +config FPGA_DFL_FME + tristate "FPGA DFL FME Driver" + depends on FPGA_DFL + help + The FPGA Management Engine (FME) is a feature device implemented + under Device Feature List (DFL) framework. Select this option to + enable the platform device driver for FME which implements all + FPGA platform level management features. There shall be one FME + per DFL based FPGA device. + config FPGA_DFL_PCI tristate "FPGA DFL PCIe Device Driver" depends on PCI && FPGA_DFL diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile index 02e025398cd5..db11f340ba0f 100644 --- a/drivers/fpga/Makefile +++ b/drivers/fpga/Makefile @@ -31,6 +31,9 @@ obj-$(CONFIG_OF_FPGA_REGION) += of-fpga-region.o # FPGA Device Feature List Support obj-$(CONFIG_FPGA_DFL) += dfl.o +obj-$(CONFIG_FPGA_DFL_FME) += dfl-fme.o + +dfl-fme-objs := dfl-fme-main.o # Drivers for FPGAs which implement DFL obj-$(CONFIG_FPGA_DFL_PCI) += dfl-pci.o diff --git a/drivers/fpga/dfl-fme-main.c b/drivers/fpga/dfl-fme-main.c new file mode 100644 index 000000000000..bdcfe951d939 --- /dev/null +++ b/drivers/fpga/dfl-fme-main.c @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for FPGA Management Engine (FME) + * + * Copyright (C) 2017-2018 Intel Corporation, Inc. + * + * Authors: + * Kang Luwei + * Xiao Guangrong + * Joseph Grecco + * Enno Luebbers + * Tim Whisonant + * Ananda Ravuri + * Henry Mitchel + */ + +#include +#include + +#include "dfl.h" + +static int fme_hdr_init(struct platform_device *pdev, + struct dfl_feature *feature) +{ + dev_dbg(&pdev->dev, "FME HDR Init.\n"); + + return 0; +} + +static void fme_hdr_uinit(struct platform_device *pdev, + struct dfl_feature *feature) +{ + dev_dbg(&pdev->dev, "FME HDR UInit.\n"); +} + +static const struct dfl_feature_ops fme_hdr_ops = { + .init = fme_hdr_init, + .uinit = fme_hdr_uinit, +}; + +static struct dfl_feature_driver fme_feature_drvs[] = { + { + .id = FME_FEATURE_ID_HEADER, + .ops = &fme_hdr_ops, + }, + { + .ops = NULL, + }, +}; + +static int fme_open(struct inode *inode, struct file *filp) +{ + struct platform_device *fdev = dfl_fpga_inode_to_feature_dev(inode); + struct dfl_feature_platform_data *pdata = dev_get_platdata(&fdev->dev); + int ret; + + if (WARN_ON(!pdata)) + return -ENODEV; + + ret = dfl_feature_dev_use_begin(pdata); + if (ret) + return ret; + + dev_dbg(&fdev->dev, "Device File Open\n"); + filp->private_data = pdata; + + return 0; +} + +static int fme_release(struct inode *inode, struct file *filp) +{ + struct dfl_feature_platform_data *pdata = filp->private_data; + struct platform_device *pdev = pdata->dev; + + dev_dbg(&pdev->dev, "Device File Release\n"); + dfl_feature_dev_use_end(pdata); + + return 0; +} + +static long fme_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + struct dfl_feature_platform_data *pdata = filp->private_data; + struct platform_device *pdev = pdata->dev; + struct dfl_feature *f; + long ret; + + dev_dbg(&pdev->dev, "%s cmd 0x%x\n", __func__, cmd); + + switch (cmd) { + default: + /* + * Let sub-feature's ioctl function to handle the cmd. + * Sub-feature's ioctl returns -ENODEV when cmd is not + * handled in this sub feature, and returns 0 or other + * error code if cmd is handled. + */ + dfl_fpga_dev_for_each_feature(pdata, f) { + if (f->ops && f->ops->ioctl) { + ret = f->ops->ioctl(pdev, f, cmd, arg); + if (ret != -ENODEV) + return ret; + } + } + } + + return -EINVAL; +} + +static const struct file_operations fme_fops = { + .owner = THIS_MODULE, + .open = fme_open, + .release = fme_release, + .unlocked_ioctl = fme_ioctl, +}; + +static int fme_probe(struct platform_device *pdev) +{ + int ret; + + ret = dfl_fpga_dev_feature_init(pdev, fme_feature_drvs); + if (ret) + goto exit; + + ret = dfl_fpga_dev_ops_register(pdev, &fme_fops, THIS_MODULE); + if (ret) + goto feature_uinit; + + return 0; + +feature_uinit: + dfl_fpga_dev_feature_uinit(pdev); +exit: + return ret; +} + +static int fme_remove(struct platform_device *pdev) +{ + dfl_fpga_dev_ops_unregister(pdev); + dfl_fpga_dev_feature_uinit(pdev); + + return 0; +} + +static struct platform_driver fme_driver = { + .driver = { + .name = DFL_FPGA_FEATURE_DEV_FME, + }, + .probe = fme_probe, + .remove = fme_remove, +}; + +module_platform_driver(fme_driver); + +MODULE_DESCRIPTION("FPGA Management Engine driver"); +MODULE_AUTHOR("Intel Corporation"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:dfl-fme"); -- cgit v1.2.3 From 0a27ff24d59662b1ca8b3f7721a965918f115074 Mon Sep 17 00:00:00 2001 From: Kang Luwei Date: Sat, 30 Jun 2018 08:53:22 +0800 Subject: fpga: dfl: fme: add header sub feature support The Header Register set is always present for FPGA Management Engine (FME), this patch implements init and uinit function for header sub feature and introduces several read-only sysfs interfaces for the capability and status. Sysfs interfaces: * /sys/class/fpga_region///ports_num Read-only. Number of ports implemented * /sys/class/fpga_region///bitstream_id Read-only. Bitstream (static FPGA region) identifier number. It contains the detailed version and other information of this static FPGA region. * /sys/class/fpga_region///bitstream_metadata Read-only. Bitstream (static FPGA region) meta data. It contains the synthesis date, seed and other information of this static FPGA region. Signed-off-by: Tim Whisonant Signed-off-by: Enno Luebbers Signed-off-by: Shiva Rao Signed-off-by: Christopher Rauer Signed-off-by: Kang Luwei Signed-off-by: Xiao Guangrong Signed-off-by: Wu Hao Acked-by: Alan Tull Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-platform-dfl-fme | 23 ++++++++ drivers/fpga/dfl-fme-main.c | 68 ++++++++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-platform-dfl-fme diff --git a/Documentation/ABI/testing/sysfs-platform-dfl-fme b/Documentation/ABI/testing/sysfs-platform-dfl-fme new file mode 100644 index 000000000000..8fa4febfa4b2 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-platform-dfl-fme @@ -0,0 +1,23 @@ +What: /sys/bus/platform/devices/dfl-fme.0/ports_num +Date: June 2018 +KernelVersion: 4.19 +Contact: Wu Hao +Description: Read-only. One DFL FPGA device may have more than 1 + port/Accelerator Function Unit (AFU). It returns the + number of ports on the FPGA device when read it. + +What: /sys/bus/platform/devices/dfl-fme.0/bitstream_id +Date: June 2018 +KernelVersion: 4.19 +Contact: Wu Hao +Description: Read-only. It returns Bitstream (static FPGA region) + identifier number, which includes the detailed version + and other information of this static FPGA region. + +What: /sys/bus/platform/devices/dfl-fme.0/bitstream_metadata +Date: June 2018 +KernelVersion: 4.19 +Contact: Wu Hao +Description: Read-only. It returns Bitstream (static FPGA region) meta + data, which includes the synthesis date, seed and other + information of this static FPGA region. diff --git a/drivers/fpga/dfl-fme-main.c b/drivers/fpga/dfl-fme-main.c index bdcfe951d939..c23c56fe3f4b 100644 --- a/drivers/fpga/dfl-fme-main.c +++ b/drivers/fpga/dfl-fme-main.c @@ -19,10 +19,77 @@ #include "dfl.h" +static ssize_t ports_num_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + void __iomem *base; + u64 v; + + base = dfl_get_feature_ioaddr_by_id(dev, FME_FEATURE_ID_HEADER); + + v = readq(base + FME_HDR_CAP); + + return scnprintf(buf, PAGE_SIZE, "%u\n", + (unsigned int)FIELD_GET(FME_CAP_NUM_PORTS, v)); +} +static DEVICE_ATTR_RO(ports_num); + +/* + * Bitstream (static FPGA region) identifier number. It contains the + * detailed version and other information of this static FPGA region. + */ +static ssize_t bitstream_id_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + void __iomem *base; + u64 v; + + base = dfl_get_feature_ioaddr_by_id(dev, FME_FEATURE_ID_HEADER); + + v = readq(base + FME_HDR_BITSTREAM_ID); + + return scnprintf(buf, PAGE_SIZE, "0x%llx\n", (unsigned long long)v); +} +static DEVICE_ATTR_RO(bitstream_id); + +/* + * Bitstream (static FPGA region) meta data. It contains the synthesis + * date, seed and other information of this static FPGA region. + */ +static ssize_t bitstream_metadata_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + void __iomem *base; + u64 v; + + base = dfl_get_feature_ioaddr_by_id(dev, FME_FEATURE_ID_HEADER); + + v = readq(base + FME_HDR_BITSTREAM_MD); + + return scnprintf(buf, PAGE_SIZE, "0x%llx\n", (unsigned long long)v); +} +static DEVICE_ATTR_RO(bitstream_metadata); + +static const struct attribute *fme_hdr_attrs[] = { + &dev_attr_ports_num.attr, + &dev_attr_bitstream_id.attr, + &dev_attr_bitstream_metadata.attr, + NULL, +}; + static int fme_hdr_init(struct platform_device *pdev, struct dfl_feature *feature) { + void __iomem *base = feature->ioaddr; + int ret; + dev_dbg(&pdev->dev, "FME HDR Init.\n"); + dev_dbg(&pdev->dev, "FME cap %llx.\n", + (unsigned long long)readq(base + FME_HDR_CAP)); + + ret = sysfs_create_files(&pdev->dev.kobj, fme_hdr_attrs); + if (ret) + return ret; return 0; } @@ -31,6 +98,7 @@ static void fme_hdr_uinit(struct platform_device *pdev, struct dfl_feature *feature) { dev_dbg(&pdev->dev, "FME HDR UInit.\n"); + sysfs_remove_files(&pdev->dev.kobj, fme_hdr_attrs); } static const struct dfl_feature_ops fme_hdr_ops = { -- cgit v1.2.3 From 620e1902f6fe57ddacdabd9e33fadbd290be9652 Mon Sep 17 00:00:00 2001 From: Wu Hao Date: Sat, 30 Jun 2018 08:53:23 +0800 Subject: fpga: dfl: fme: add DFL_FPGA_GET_API_VERSION/CHECK_EXTENSION ioctls support DFL_FPGA_GET_API_VERSION and DFL_FPGA_CHECK_EXTENSION ioctls are common ones which need to be supported by all feature devices drivers including FME and AFU. Userspace application can use these ioctl interfaces to get the API info and check if specific extension is supported or not in current driver. This patch implements above 2 ioctls in FPGA Management Engine (FME) driver. Signed-off-by: Tim Whisonant Signed-off-by: Enno Luebbers Signed-off-by: Shiva Rao Signed-off-by: Christopher Rauer Signed-off-by: Xiao Guangrong Signed-off-by: Wu Hao Acked-by: Alan Tull Acked-by: Moritz Fischer Signed-off-by: Greg Kroah-Hartman --- Documentation/ioctl/ioctl-number.txt | 1 + drivers/fpga/dfl-fme-main.c | 12 +++++++++ include/uapi/linux/fpga-dfl.h | 50 ++++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+) create mode 100644 include/uapi/linux/fpga-dfl.h diff --git a/Documentation/ioctl/ioctl-number.txt b/Documentation/ioctl/ioctl-number.txt index 480c8609dc58..db9afea24299 100644 --- a/Documentation/ioctl/ioctl-number.txt +++ b/Documentation/ioctl/ioctl-number.txt @@ -322,6 +322,7 @@ Code Seq#(hex) Include File Comments 0xB3 00 linux/mmc/ioctl.h 0xB4 00-0F linux/gpio.h 0xB5 00-0F uapi/linux/rpmsg.h +0xB6 all linux/fpga-dfl.h 0xC0 00-0F linux/usb/iowarrior.h 0xCA 00-0F uapi/misc/cxl.h 0xCA 10-2F uapi/misc/ocxl.h diff --git a/drivers/fpga/dfl-fme-main.c b/drivers/fpga/dfl-fme-main.c index c23c56fe3f4b..c83ff88e3bbb 100644 --- a/drivers/fpga/dfl-fme-main.c +++ b/drivers/fpga/dfl-fme-main.c @@ -16,6 +16,7 @@ #include #include +#include #include "dfl.h" @@ -116,6 +117,13 @@ static struct dfl_feature_driver fme_feature_drvs[] = { }, }; +static long fme_ioctl_check_extension(struct dfl_feature_platform_data *pdata, + unsigned long arg) +{ + /* No extension support for now */ + return 0; +} + static int fme_open(struct inode *inode, struct file *filp) { struct platform_device *fdev = dfl_fpga_inode_to_feature_dev(inode); @@ -156,6 +164,10 @@ static long fme_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) dev_dbg(&pdev->dev, "%s cmd 0x%x\n", __func__, cmd); switch (cmd) { + case DFL_FPGA_GET_API_VERSION: + return DFL_FPGA_API_VERSION; + case DFL_FPGA_CHECK_EXTENSION: + return fme_ioctl_check_extension(pdata, arg); default: /* * Let sub-feature's ioctl function to handle the cmd. diff --git a/include/uapi/linux/fpga-dfl.h b/include/uapi/linux/fpga-dfl.h new file mode 100644 index 000000000000..858e4437c31c --- /dev/null +++ b/include/uapi/linux/fpga-dfl.h @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * Header File for FPGA DFL User API + * + * Copyright (C) 2017-2018 Intel Corporation, Inc. + * + * Authors: + * Kang Luwei + * Zhang Yi + * Wu Hao + * Xiao Guangrong + */ + +#ifndef _UAPI_LINUX_FPGA_DFL_H +#define _UAPI_LINUX_FPGA_DFL_H + +#include + +#define DFL_FPGA_API_VERSION 0 + +/* + * The IOCTL interface for DFL based FPGA is designed for extensibility by + * embedding the structure length (argsz) and flags into structures passed + * between kernel and userspace. This design referenced the VFIO IOCTL + * interface (include/uapi/linux/vfio.h). + */ + +#define DFL_FPGA_MAGIC 0xB6 + +#define DFL_FPGA_BASE 0 + +/** + * DFL_FPGA_GET_API_VERSION - _IO(DFL_FPGA_MAGIC, DFL_FPGA_BASE + 0) + * + * Report the version of the driver API. + * Return: Driver API Version. + */ + +#define DFL_FPGA_GET_API_VERSION _IO(DFL_FPGA_MAGIC, DFL_FPGA_BASE + 0) + +/** + * DFL_FPGA_CHECK_EXTENSION - _IO(DFL_FPGA_MAGIC, DFL_FPGA_BASE + 1) + * + * Check whether an extension is supported. + * Return: 0 if not supported, otherwise the extension is supported. + */ + +#define DFL_FPGA_CHECK_EXTENSION _IO(DFL_FPGA_MAGIC, DFL_FPGA_BASE + 1) + +#endif /* _UAPI_LINUX_FPGA_DFL_H */ -- cgit v1.2.3 From 29de76240e861d52b75405166337e94184f1875d Mon Sep 17 00:00:00 2001 From: Kang Luwei Date: Sat, 30 Jun 2018 08:53:24 +0800 Subject: fpga: dfl: fme: add partial reconfiguration sub feature support Partial Reconfiguration (PR) is the most important function for FME. It allows reconfiguration for given Port/Accelerated Function Unit (AFU). It creates platform devices for fpga-mgr, fpga-regions and fpga-bridges, and invokes fpga-region's interface (fpga_region_program_fpga) for PR operation once PR request received via ioctl. Below user space interface is exposed by this sub feature. Ioctl interface: * DFL_FPGA_FME_PORT_PR Do partial reconfiguration per information from userspace, including target port(AFU), buffer size and address info. It returns error code to userspace if failed. For detailed PR error information, user needs to read fpga-mgr's status sysfs interface. Signed-off-by: Tim Whisonant Signed-off-by: Enno Luebbers Signed-off-by: Shiva Rao Signed-off-by: Christopher Rauer Signed-off-by: Kang Luwei Signed-off-by: Xiao Guangrong Signed-off-by: Wu Hao Acked-by: Alan Tull Signed-off-by: Greg Kroah-Hartman --- drivers/fpga/Makefile | 2 +- drivers/fpga/dfl-fme-main.c | 43 +++- drivers/fpga/dfl-fme-pr.c | 479 ++++++++++++++++++++++++++++++++++++++++++ drivers/fpga/dfl-fme-pr.h | 84 ++++++++ drivers/fpga/dfl-fme.h | 38 ++++ include/uapi/linux/fpga-dfl.h | 27 +++ 6 files changed, 671 insertions(+), 2 deletions(-) create mode 100644 drivers/fpga/dfl-fme-pr.c create mode 100644 drivers/fpga/dfl-fme-pr.h create mode 100644 drivers/fpga/dfl-fme.h diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile index db11f340ba0f..fd334d40aa1c 100644 --- a/drivers/fpga/Makefile +++ b/drivers/fpga/Makefile @@ -33,7 +33,7 @@ obj-$(CONFIG_OF_FPGA_REGION) += of-fpga-region.o obj-$(CONFIG_FPGA_DFL) += dfl.o obj-$(CONFIG_FPGA_DFL_FME) += dfl-fme.o -dfl-fme-objs := dfl-fme-main.o +dfl-fme-objs := dfl-fme-main.o dfl-fme-pr.o # Drivers for FPGAs which implement DFL obj-$(CONFIG_FPGA_DFL_PCI) += dfl-pci.o diff --git a/drivers/fpga/dfl-fme-main.c b/drivers/fpga/dfl-fme-main.c index c83ff88e3bbb..086ad2420ade 100644 --- a/drivers/fpga/dfl-fme-main.c +++ b/drivers/fpga/dfl-fme-main.c @@ -19,6 +19,7 @@ #include #include "dfl.h" +#include "dfl-fme.h" static ssize_t ports_num_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -112,6 +113,10 @@ static struct dfl_feature_driver fme_feature_drvs[] = { .id = FME_FEATURE_ID_HEADER, .ops = &fme_hdr_ops, }, + { + .id = FME_FEATURE_ID_PR_MGMT, + .ops = &pr_mgmt_ops, + }, { .ops = NULL, }, @@ -187,6 +192,35 @@ static long fme_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) return -EINVAL; } +static int fme_dev_init(struct platform_device *pdev) +{ + struct dfl_feature_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct dfl_fme *fme; + + fme = devm_kzalloc(&pdev->dev, sizeof(*fme), GFP_KERNEL); + if (!fme) + return -ENOMEM; + + fme->pdata = pdata; + + mutex_lock(&pdata->lock); + dfl_fpga_pdata_set_private(pdata, fme); + mutex_unlock(&pdata->lock); + + return 0; +} + +static void fme_dev_destroy(struct platform_device *pdev) +{ + struct dfl_feature_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct dfl_fme *fme; + + mutex_lock(&pdata->lock); + fme = dfl_fpga_pdata_get_private(pdata); + dfl_fpga_pdata_set_private(pdata, NULL); + mutex_unlock(&pdata->lock); +} + static const struct file_operations fme_fops = { .owner = THIS_MODULE, .open = fme_open, @@ -198,10 +232,14 @@ static int fme_probe(struct platform_device *pdev) { int ret; - ret = dfl_fpga_dev_feature_init(pdev, fme_feature_drvs); + ret = fme_dev_init(pdev); if (ret) goto exit; + ret = dfl_fpga_dev_feature_init(pdev, fme_feature_drvs); + if (ret) + goto dev_destroy; + ret = dfl_fpga_dev_ops_register(pdev, &fme_fops, THIS_MODULE); if (ret) goto feature_uinit; @@ -210,6 +248,8 @@ static int fme_probe(struct platform_device *pdev) feature_uinit: dfl_fpga_dev_feature_uinit(pdev); +dev_destroy: + fme_dev_destroy(pdev); exit: return ret; } @@ -218,6 +258,7 @@ static int fme_remove(struct platform_device *pdev) { dfl_fpga_dev_ops_unregister(pdev); dfl_fpga_dev_feature_uinit(pdev); + fme_dev_destroy(pdev); return 0; } diff --git a/drivers/fpga/dfl-fme-pr.c b/drivers/fpga/dfl-fme-pr.c new file mode 100644 index 000000000000..fc9fd2d0482f --- /dev/null +++ b/drivers/fpga/dfl-fme-pr.c @@ -0,0 +1,479 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for FPGA Management Engine (FME) Partial Reconfiguration + * + * Copyright (C) 2017-2018 Intel Corporation, Inc. + * + * Authors: + * Kang Luwei + * Xiao Guangrong + * Wu Hao + * Joseph Grecco + * Enno Luebbers + * Tim Whisonant + * Ananda Ravuri + * Christopher Rauer + * Henry Mitchel + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dfl.h" +#include "dfl-fme.h" +#include "dfl-fme-pr.h" + +static struct dfl_fme_region * +dfl_fme_region_find_by_port_id(struct dfl_fme *fme, int port_id) +{ + struct dfl_fme_region *fme_region; + + list_for_each_entry(fme_region, &fme->region_list, node) + if (fme_region->port_id == port_id) + return fme_region; + + return NULL; +} + +static int dfl_fme_region_match(struct device *dev, const void *data) +{ + return dev->parent == data; +} + +static struct fpga_region *dfl_fme_region_find(struct dfl_fme *fme, int port_id) +{ + struct dfl_fme_region *fme_region; + struct fpga_region *region; + + fme_region = dfl_fme_region_find_by_port_id(fme, port_id); + if (!fme_region) + return NULL; + + region = fpga_region_class_find(NULL, &fme_region->region->dev, + dfl_fme_region_match); + if (!region) + return NULL; + + return region; +} + +static int fme_pr(struct platform_device *pdev, unsigned long arg) +{ + struct dfl_feature_platform_data *pdata = dev_get_platdata(&pdev->dev); + void __user *argp = (void __user *)arg; + struct dfl_fpga_fme_port_pr port_pr; + struct fpga_image_info *info; + struct fpga_region *region; + void __iomem *fme_hdr; + struct dfl_fme *fme; + unsigned long minsz; + void *buf = NULL; + int ret = 0; + u64 v; + + minsz = offsetofend(struct dfl_fpga_fme_port_pr, buffer_address); + + if (copy_from_user(&port_pr, argp, minsz)) + return -EFAULT; + + if (port_pr.argsz < minsz || port_pr.flags) + return -EINVAL; + + if (!IS_ALIGNED(port_pr.buffer_size, 4)) + return -EINVAL; + + /* get fme header region */ + fme_hdr = dfl_get_feature_ioaddr_by_id(&pdev->dev, + FME_FEATURE_ID_HEADER); + + /* check port id */ + v = readq(fme_hdr + FME_HDR_CAP); + if (port_pr.port_id >= FIELD_GET(FME_CAP_NUM_PORTS, v)) { + dev_dbg(&pdev->dev, "port number more than maximum\n"); + return -EINVAL; + } + + if (!access_ok(VERIFY_READ, + (void __user *)(unsigned long)port_pr.buffer_address, + port_pr.buffer_size)) + return -EFAULT; + + buf = vmalloc(port_pr.buffer_size); + if (!buf) + return -ENOMEM; + + if (copy_from_user(buf, + (void __user *)(unsigned long)port_pr.buffer_address, + port_pr.buffer_size)) { + ret = -EFAULT; + goto free_exit; + } + + /* prepare fpga_image_info for PR */ + info = fpga_image_info_alloc(&pdev->dev); + if (!info) { + ret = -ENOMEM; + goto free_exit; + } + + info->flags |= FPGA_MGR_PARTIAL_RECONFIG; + + mutex_lock(&pdata->lock); + fme = dfl_fpga_pdata_get_private(pdata); + /* fme device has been unregistered. */ + if (!fme) { + ret = -EINVAL; + goto unlock_exit; + } + + region = dfl_fme_region_find(fme, port_pr.port_id); + if (!region) { + ret = -EINVAL; + goto unlock_exit; + } + + fpga_image_info_free(region->info); + + info->buf = buf; + info->count = port_pr.buffer_size; + info->region_id = port_pr.port_id; + region->info = info; + + ret = fpga_region_program_fpga(region); + + /* + * it allows userspace to reset the PR region's logic by disabling and + * reenabling the bridge to clear things out between accleration runs. + * so no need to hold the bridges after partial reconfiguration. + */ + if (region->get_bridges) + fpga_bridges_put(®ion->bridge_list); + + put_device(®ion->dev); +unlock_exit: + mutex_unlock(&pdata->lock); +free_exit: + vfree(buf); + if (copy_to_user((void __user *)arg, &port_pr, minsz)) + return -EFAULT; + + return ret; +} + +/** + * dfl_fme_create_mgr - create fpga mgr platform device as child device + * + * @pdata: fme platform_device's pdata + * + * Return: mgr platform device if successful, and error code otherwise. + */ +static struct platform_device * +dfl_fme_create_mgr(struct dfl_feature_platform_data *pdata, + struct dfl_feature *feature) +{ + struct platform_device *mgr, *fme = pdata->dev; + struct dfl_fme_mgr_pdata mgr_pdata; + int ret = -ENOMEM; + + if (!feature->ioaddr) + return ERR_PTR(-ENODEV); + + mgr_pdata.ioaddr = feature->ioaddr; + + /* + * Each FME has only one fpga-mgr, so allocate platform device using + * the same FME platform device id. + */ + mgr = platform_device_alloc(DFL_FPGA_FME_MGR, fme->id); + if (!mgr) + return ERR_PTR(ret); + + mgr->dev.parent = &fme->dev; + + ret = platform_device_add_data(mgr, &mgr_pdata, sizeof(mgr_pdata)); + if (ret) + goto create_mgr_err; + + ret = platform_device_add(mgr); + if (ret) + goto create_mgr_err; + + return mgr; + +create_mgr_err: + platform_device_put(mgr); + return ERR_PTR(ret); +} + +/** + * dfl_fme_destroy_mgr - destroy fpga mgr platform device + * @pdata: fme platform device's pdata + */ +static void dfl_fme_destroy_mgr(struct dfl_feature_platform_data *pdata) +{ + struct dfl_fme *priv = dfl_fpga_pdata_get_private(pdata); + + platform_device_unregister(priv->mgr); +} + +/** + * dfl_fme_create_bridge - create fme fpga bridge platform device as child + * + * @pdata: fme platform device's pdata + * @port_id: port id for the bridge to be created. + * + * Return: bridge platform device if successful, and error code otherwise. + */ +static struct dfl_fme_bridge * +dfl_fme_create_bridge(struct dfl_feature_platform_data *pdata, int port_id) +{ + struct device *dev = &pdata->dev->dev; + struct dfl_fme_br_pdata br_pdata; + struct dfl_fme_bridge *fme_br; + int ret = -ENOMEM; + + fme_br = devm_kzalloc(dev, sizeof(*fme_br), GFP_KERNEL); + if (!fme_br) + return ERR_PTR(ret); + + br_pdata.cdev = pdata->dfl_cdev; + br_pdata.port_id = port_id; + + fme_br->br = platform_device_alloc(DFL_FPGA_FME_BRIDGE, + PLATFORM_DEVID_AUTO); + if (!fme_br->br) + return ERR_PTR(ret); + + fme_br->br->dev.parent = dev; + + ret = platform_device_add_data(fme_br->br, &br_pdata, sizeof(br_pdata)); + if (ret) + goto create_br_err; + + ret = platform_device_add(fme_br->br); + if (ret) + goto create_br_err; + + return fme_br; + +create_br_err: + platform_device_put(fme_br->br); + return ERR_PTR(ret); +} + +/** + * dfl_fme_destroy_bridge - destroy fpga bridge platform device + * @fme_br: fme bridge to destroy + */ +static void dfl_fme_destroy_bridge(struct dfl_fme_bridge *fme_br) +{ + platform_device_unregister(fme_br->br); +} + +/** + * dfl_fme_destroy_bridge - destroy all fpga bridge platform device + * @pdata: fme platform device's pdata + */ +static void dfl_fme_destroy_bridges(struct dfl_feature_platform_data *pdata) +{ + struct dfl_fme *priv = dfl_fpga_pdata_get_private(pdata); + struct dfl_fme_bridge *fbridge, *tmp; + + list_for_each_entry_safe(fbridge, tmp, &priv->bridge_list, node) { + list_del(&fbridge->node); + dfl_fme_destroy_bridge(fbridge); + } +} + +/** + * dfl_fme_create_region - create fpga region platform device as child + * + * @pdata: fme platform device's pdata + * @mgr: mgr platform device needed for region + * @br: br platform device needed for region + * @port_id: port id + * + * Return: fme region if successful, and error code otherwise. + */ +static struct dfl_fme_region * +dfl_fme_create_region(struct dfl_feature_platform_data *pdata, + struct platform_device *mgr, + struct platform_device *br, int port_id) +{ + struct dfl_fme_region_pdata region_pdata; + struct device *dev = &pdata->dev->dev; + struct dfl_fme_region *fme_region; + int ret = -ENOMEM; + + fme_region = devm_kzalloc(dev, sizeof(*fme_region), GFP_KERNEL); + if (!fme_region) + return ERR_PTR(ret); + + region_pdata.mgr = mgr; + region_pdata.br = br; + + /* + * Each FPGA device may have more than one port, so allocate platform + * device using the same port platform device id. + */ + fme_region->region = platform_device_alloc(DFL_FPGA_FME_REGION, br->id); + if (!fme_region->region) + return ERR_PTR(ret); + + fme_region->region->dev.parent = dev; + + ret = platform_device_add_data(fme_region->region, ®ion_pdata, + sizeof(region_pdata)); + if (ret) + goto create_region_err; + + ret = platform_device_add(fme_region->region); + if (ret) + goto create_region_err; + + fme_region->port_id = port_id; + + return fme_region; + +create_region_err: + platform_device_put(fme_region->region); + return ERR_PTR(ret); +} + +/** + * dfl_fme_destroy_region - destroy fme region + * @fme_region: fme region to destroy + */ +static void dfl_fme_destroy_region(struct dfl_fme_region *fme_region) +{ + platform_device_unregister(fme_region->region); +} + +/** + * dfl_fme_destroy_regions - destroy all fme regions + * @pdata: fme platform device's pdata + */ +static void dfl_fme_destroy_regions(struct dfl_feature_platform_data *pdata) +{ + struct dfl_fme *priv = dfl_fpga_pdata_get_private(pdata); + struct dfl_fme_region *fme_region, *tmp; + + list_for_each_entry_safe(fme_region, tmp, &priv->region_list, node) { + list_del(&fme_region->node); + dfl_fme_destroy_region(fme_region); + } +} + +static int pr_mgmt_init(struct platform_device *pdev, + struct dfl_feature *feature) +{ + struct dfl_feature_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct dfl_fme_region *fme_region; + struct dfl_fme_bridge *fme_br; + struct platform_device *mgr; + struct dfl_fme *priv; + void __iomem *fme_hdr; + int ret = -ENODEV, i = 0; + u64 fme_cap, port_offset; + + fme_hdr = dfl_get_feature_ioaddr_by_id(&pdev->dev, + FME_FEATURE_ID_HEADER); + + mutex_lock(&pdata->lock); + priv = dfl_fpga_pdata_get_private(pdata); + + /* Initialize the region and bridge sub device list */ + INIT_LIST_HEAD(&priv->region_list); + INIT_LIST_HEAD(&priv->bridge_list); + + /* Create fpga mgr platform device */ + mgr = dfl_fme_create_mgr(pdata, feature); + if (IS_ERR(mgr)) { + dev_err(&pdev->dev, "fail to create fpga mgr pdev\n"); + goto unlock; + } + + priv->mgr = mgr; + + /* Read capability register to check number of regions and bridges */ + fme_cap = readq(fme_hdr + FME_HDR_CAP); + for (; i < FIELD_GET(FME_CAP_NUM_PORTS, fme_cap); i++) { + port_offset = readq(fme_hdr + FME_HDR_PORT_OFST(i)); + if (!(port_offset & FME_PORT_OFST_IMP)) + continue; + + /* Create bridge for each port */ + fme_br = dfl_fme_create_bridge(pdata, i); + if (IS_ERR(fme_br)) { + ret = PTR_ERR(fme_br); + goto destroy_region; + } + + list_add(&fme_br->node, &priv->bridge_list); + + /* Create region for each port */ + fme_region = dfl_fme_create_region(pdata, mgr, + fme_br->br, i); + if (!fme_region) { + ret = PTR_ERR(fme_region); + goto destroy_region; + } + + list_add(&fme_region->node, &priv->region_list); + } + mutex_unlock(&pdata->lock); + + return 0; + +destroy_region: + dfl_fme_destroy_regions(pdata); + dfl_fme_destroy_bridges(pdata); + dfl_fme_destroy_mgr(pdata); +unlock: + mutex_unlock(&pdata->lock); + return ret; +} + +static void pr_mgmt_uinit(struct platform_device *pdev, + struct dfl_feature *feature) +{ + struct dfl_feature_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct dfl_fme *priv; + + mutex_lock(&pdata->lock); + priv = dfl_fpga_pdata_get_private(pdata); + + dfl_fme_destroy_regions(pdata); + dfl_fme_destroy_bridges(pdata); + dfl_fme_destroy_mgr(pdata); + mutex_unlock(&pdata->lock); +} + +static long fme_pr_ioctl(struct platform_device *pdev, + struct dfl_feature *feature, + unsigned int cmd, unsigned long arg) +{ + long ret; + + switch (cmd) { + case DFL_FPGA_FME_PORT_PR: + ret = fme_pr(pdev, arg); + break; + default: + ret = -ENODEV; + } + + return ret; +} + +const struct dfl_feature_ops pr_mgmt_ops = { + .init = pr_mgmt_init, + .uinit = pr_mgmt_uinit, + .ioctl = fme_pr_ioctl, +}; diff --git a/drivers/fpga/dfl-fme-pr.h b/drivers/fpga/dfl-fme-pr.h new file mode 100644 index 000000000000..096a699089d3 --- /dev/null +++ b/drivers/fpga/dfl-fme-pr.h @@ -0,0 +1,84 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Header file for FPGA Management Engine (FME) Partial Reconfiguration Driver + * + * Copyright (C) 2017-2018 Intel Corporation, Inc. + * + * Authors: + * Kang Luwei + * Xiao Guangrong + * Wu Hao + * Joseph Grecco + * Enno Luebbers + * Tim Whisonant + * Ananda Ravuri + * Henry Mitchel + */ + +#ifndef __DFL_FME_PR_H +#define __DFL_FME_PR_H + +#include + +/** + * struct dfl_fme_region - FME fpga region data structure + * + * @region: platform device of the FPGA region. + * @node: used to link fme_region to a list. + * @port_id: indicate which port this region connected to. + */ +struct dfl_fme_region { + struct platform_device *region; + struct list_head node; + int port_id; +}; + +/** + * struct dfl_fme_region_pdata - platform data for FME region platform device. + * + * @mgr: platform device of the FPGA manager. + * @br: platform device of the FPGA bridge. + * @region_id: region id (same as port_id). + */ +struct dfl_fme_region_pdata { + struct platform_device *mgr; + struct platform_device *br; + int region_id; +}; + +/** + * struct dfl_fme_bridge - FME fpga bridge data structure + * + * @br: platform device of the FPGA bridge. + * @node: used to link fme_bridge to a list. + */ +struct dfl_fme_bridge { + struct platform_device *br; + struct list_head node; +}; + +/** + * struct dfl_fme_bridge_pdata - platform data for FME bridge platform device. + * + * @cdev: container device. + * @port_id: port id. + */ +struct dfl_fme_br_pdata { + struct dfl_fpga_cdev *cdev; + int port_id; +}; + +/** + * struct dfl_fme_mgr_pdata - platform data for FME manager platform device. + * + * @ioaddr: mapped io address for FME manager platform device. + */ +struct dfl_fme_mgr_pdata { + void __iomem *ioaddr; +}; + +#define DFL_FPGA_FME_MGR "dfl-fme-mgr" +#define DFL_FPGA_FME_BRIDGE "dfl-fme-bridge" +#define DFL_FPGA_FME_REGION "dfl-fme-region" + +#endif /* __DFL_FME_PR_H */ diff --git a/drivers/fpga/dfl-fme.h b/drivers/fpga/dfl-fme.h new file mode 100644 index 000000000000..5394a216c5c0 --- /dev/null +++ b/drivers/fpga/dfl-fme.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Header file for FPGA Management Engine (FME) Driver + * + * Copyright (C) 2017-2018 Intel Corporation, Inc. + * + * Authors: + * Kang Luwei + * Xiao Guangrong + * Wu Hao + * Joseph Grecco + * Enno Luebbers + * Tim Whisonant + * Ananda Ravuri + * Henry Mitchel + */ + +#ifndef __DFL_FME_H +#define __DFL_FME_H + +/** + * struct dfl_fme - dfl fme private data + * + * @mgr: FME's FPGA manager platform device. + * @region_list: linked list of FME's FPGA regions. + * @bridge_list: linked list of FME's FPGA bridges. + * @pdata: fme platform device's pdata. + */ +struct dfl_fme { + struct platform_device *mgr; + struct list_head region_list; + struct list_head bridge_list; + struct dfl_feature_platform_data *pdata; +}; + +extern const struct dfl_feature_ops pr_mgmt_ops; + +#endif /* __DFL_FME_H */ diff --git a/include/uapi/linux/fpga-dfl.h b/include/uapi/linux/fpga-dfl.h index 858e4437c31c..9666af85a8f5 100644 --- a/include/uapi/linux/fpga-dfl.h +++ b/include/uapi/linux/fpga-dfl.h @@ -14,6 +14,7 @@ #ifndef _UAPI_LINUX_FPGA_DFL_H #define _UAPI_LINUX_FPGA_DFL_H +#include #include #define DFL_FPGA_API_VERSION 0 @@ -28,6 +29,7 @@ #define DFL_FPGA_MAGIC 0xB6 #define DFL_FPGA_BASE 0 +#define DFL_FME_BASE 0x80 /** * DFL_FPGA_GET_API_VERSION - _IO(DFL_FPGA_MAGIC, DFL_FPGA_BASE + 0) @@ -47,4 +49,29 @@ #define DFL_FPGA_CHECK_EXTENSION _IO(DFL_FPGA_MAGIC, DFL_FPGA_BASE + 1) +/* IOCTLs for FME file descriptor */ + +/** + * DFL_FPGA_FME_PORT_PR - _IOW(DFL_FPGA_MAGIC, DFL_FME_BASE + 0, + * struct dfl_fpga_fme_port_pr) + * + * Driver does Partial Reconfiguration based on Port ID and Buffer (Image) + * provided by caller. + * Return: 0 on success, -errno on failure. + * If DFL_FPGA_FME_PORT_PR returns -EIO, that indicates the HW has detected + * some errors during PR, under this case, the user can fetch HW error info + * from the status of FME's fpga manager. + */ + +struct dfl_fpga_fme_port_pr { + /* Input */ + __u32 argsz; /* Structure length */ + __u32 flags; /* Zero for now */ + __u32 port_id; + __u32 buffer_size; + __u64 buffer_address; /* Userspace address to the buffer for PR */ +}; + +#define DFL_FPGA_FME_PORT_PR _IO(DFL_FPGA_MAGIC, DFL_FME_BASE + 0) + #endif /* _UAPI_LINUX_FPGA_DFL_H */ -- cgit v1.2.3 From af275ec6160ba68714371cfe0575f9aa478ce02f Mon Sep 17 00:00:00 2001 From: Wu Hao Date: Sat, 30 Jun 2018 08:53:25 +0800 Subject: fpga: dfl: add fpga manager platform driver for FME This patch adds fpga manager driver for FPGA Management Engine (FME). It implements fpga_manager_ops for FPGA Partial Reconfiguration function. Signed-off-by: Tim Whisonant Signed-off-by: Enno Luebbers Signed-off-by: Shiva Rao Signed-off-by: Christopher Rauer Signed-off-by: Kang Luwei Signed-off-by: Xiao Guangrong Signed-off-by: Wu Hao Acked-by: Alan Tull Signed-off-by: Greg Kroah-Hartman --- drivers/fpga/Kconfig | 6 + drivers/fpga/Makefile | 1 + drivers/fpga/dfl-fme-mgr.c | 334 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 341 insertions(+) create mode 100644 drivers/fpga/dfl-fme-mgr.c diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig index 43803a10cdae..46b48e5164db 100644 --- a/drivers/fpga/Kconfig +++ b/drivers/fpga/Kconfig @@ -156,6 +156,12 @@ config FPGA_DFL_FME FPGA platform level management features. There shall be one FME per DFL based FPGA device. +config FPGA_DFL_FME_MGR + tristate "FPGA DFL FME Manager Driver" + depends on FPGA_DFL_FME && HAS_IOMEM + help + Say Y to enable FPGA Manager driver for FPGA Management Engine. + config FPGA_DFL_PCI tristate "FPGA DFL PCIe Device Driver" depends on PCI && FPGA_DFL diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile index fd334d40aa1c..23f41b02f894 100644 --- a/drivers/fpga/Makefile +++ b/drivers/fpga/Makefile @@ -32,6 +32,7 @@ obj-$(CONFIG_OF_FPGA_REGION) += of-fpga-region.o # FPGA Device Feature List Support obj-$(CONFIG_FPGA_DFL) += dfl.o obj-$(CONFIG_FPGA_DFL_FME) += dfl-fme.o +obj-$(CONFIG_FPGA_DFL_FME_MGR) += dfl-fme-mgr.o dfl-fme-objs := dfl-fme-main.o dfl-fme-pr.o diff --git a/drivers/fpga/dfl-fme-mgr.c b/drivers/fpga/dfl-fme-mgr.c new file mode 100644 index 000000000000..df843b51c663 --- /dev/null +++ b/drivers/fpga/dfl-fme-mgr.c @@ -0,0 +1,334 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * FPGA Manager Driver for FPGA Management Engine (FME) + * + * Copyright (C) 2017-2018 Intel Corporation, Inc. + * + * Authors: + * Kang Luwei + * Xiao Guangrong + * Wu Hao + * Joseph Grecco + * Enno Luebbers + * Tim Whisonant + * Ananda Ravuri + * Christopher Rauer + * Henry Mitchel + */ + +#include +#include +#include +#include +#include + +#include "dfl-fme-pr.h" + +/* FME Partial Reconfiguration Sub Feature Register Set */ +#define FME_PR_DFH 0x0 +#define FME_PR_CTRL 0x8 +#define FME_PR_STS 0x10 +#define FME_PR_DATA 0x18 +#define FME_PR_ERR 0x20 +#define FME_PR_INTFC_ID_H 0xA8 +#define FME_PR_INTFC_ID_L 0xB0 + +/* FME PR Control Register Bitfield */ +#define FME_PR_CTRL_PR_RST BIT_ULL(0) /* Reset PR engine */ +#define FME_PR_CTRL_PR_RSTACK BIT_ULL(4) /* Ack for PR engine reset */ +#define FME_PR_CTRL_PR_RGN_ID GENMASK_ULL(9, 7) /* PR Region ID */ +#define FME_PR_CTRL_PR_START BIT_ULL(12) /* Start to request PR service */ +#define FME_PR_CTRL_PR_COMPLETE BIT_ULL(13) /* PR data push completion */ + +/* FME PR Status Register Bitfield */ +/* Number of available entries in HW queue inside the PR engine. */ +#define FME_PR_STS_PR_CREDIT GENMASK_ULL(8, 0) +#define FME_PR_STS_PR_STS BIT_ULL(16) /* PR operation status */ +#define FME_PR_STS_PR_STS_IDLE 0 +#define FME_PR_STS_PR_CTRLR_STS GENMASK_ULL(22, 20) /* Controller status */ +#define FME_PR_STS_PR_HOST_STS GENMASK_ULL(27, 24) /* PR host status */ + +/* FME PR Data Register Bitfield */ +/* PR data from the raw-binary file. */ +#define FME_PR_DATA_PR_DATA_RAW GENMASK_ULL(32, 0) + +/* FME PR Error Register */ +/* PR Operation errors detected. */ +#define FME_PR_ERR_OPERATION_ERR BIT_ULL(0) +/* CRC error detected. */ +#define FME_PR_ERR_CRC_ERR BIT_ULL(1) +/* Incompatible PR bitstream detected. */ +#define FME_PR_ERR_INCOMPATIBLE_BS BIT_ULL(2) +/* PR data push protocol violated. */ +#define FME_PR_ERR_PROTOCOL_ERR BIT_ULL(3) +/* PR data fifo overflow error detected */ +#define FME_PR_ERR_FIFO_OVERFLOW BIT_ULL(4) + +#define PR_WAIT_TIMEOUT 8000000 +#define PR_HOST_STATUS_IDLE 0 + +struct fme_mgr_priv { + void __iomem *ioaddr; + u64 pr_error; +}; + +static u64 pr_error_to_mgr_status(u64 err) +{ + u64 status = 0; + + if (err & FME_PR_ERR_OPERATION_ERR) + status |= FPGA_MGR_STATUS_OPERATION_ERR; + if (err & FME_PR_ERR_CRC_ERR) + status |= FPGA_MGR_STATUS_CRC_ERR; + if (err & FME_PR_ERR_INCOMPATIBLE_BS) + status |= FPGA_MGR_STATUS_INCOMPATIBLE_IMAGE_ERR; + if (err & FME_PR_ERR_PROTOCOL_ERR) + status |= FPGA_MGR_STATUS_IP_PROTOCOL_ERR; + if (err & FME_PR_ERR_FIFO_OVERFLOW) + status |= FPGA_MGR_STATUS_FIFO_OVERFLOW_ERR; + + return status; +} + +static u64 fme_mgr_pr_error_handle(void __iomem *fme_pr) +{ + u64 pr_status, pr_error; + + pr_status = readq(fme_pr + FME_PR_STS); + if (!(pr_status & FME_PR_STS_PR_STS)) + return 0; + + pr_error = readq(fme_pr + FME_PR_ERR); + writeq(pr_error, fme_pr + FME_PR_ERR); + + return pr_error; +} + +static int fme_mgr_write_init(struct fpga_manager *mgr, + struct fpga_image_info *info, + const char *buf, size_t count) +{ + struct device *dev = &mgr->dev; + struct fme_mgr_priv *priv = mgr->priv; + void __iomem *fme_pr = priv->ioaddr; + u64 pr_ctrl, pr_status; + + if (!(info->flags & FPGA_MGR_PARTIAL_RECONFIG)) { + dev_err(dev, "only supports partial reconfiguration.\n"); + return -EINVAL; + } + + dev_dbg(dev, "resetting PR before initiated PR\n"); + + pr_ctrl = readq(fme_pr + FME_PR_CTRL); + pr_ctrl |= FME_PR_CTRL_PR_RST; + writeq(pr_ctrl, fme_pr + FME_PR_CTRL); + + if (readq_poll_timeout(fme_pr + FME_PR_CTRL, pr_ctrl, + pr_ctrl & FME_PR_CTRL_PR_RSTACK, 1, + PR_WAIT_TIMEOUT)) { + dev_err(dev, "PR Reset ACK timeout\n"); + return -ETIMEDOUT; + } + + pr_ctrl = readq(fme_pr + FME_PR_CTRL); + pr_ctrl &= ~FME_PR_CTRL_PR_RST; + writeq(pr_ctrl, fme_pr + FME_PR_CTRL); + + dev_dbg(dev, + "waiting for PR resource in HW to be initialized and ready\n"); + + if (readq_poll_timeout(fme_pr + FME_PR_STS, pr_status, + (pr_status & FME_PR_STS_PR_STS) == + FME_PR_STS_PR_STS_IDLE, 1, PR_WAIT_TIMEOUT)) { + dev_err(dev, "PR Status timeout\n"); + priv->pr_error = fme_mgr_pr_error_handle(fme_pr); + return -ETIMEDOUT; + } + + dev_dbg(dev, "check and clear previous PR error\n"); + priv->pr_error = fme_mgr_pr_error_handle(fme_pr); + if (priv->pr_error) + dev_dbg(dev, "previous PR error detected %llx\n", + (unsigned long long)priv->pr_error); + + dev_dbg(dev, "set PR port ID\n"); + + pr_ctrl = readq(fme_pr + FME_PR_CTRL); + pr_ctrl &= ~FME_PR_CTRL_PR_RGN_ID; + pr_ctrl |= FIELD_PREP(FME_PR_CTRL_PR_RGN_ID, info->region_id); + writeq(pr_ctrl, fme_pr + FME_PR_CTRL); + + return 0; +} + +static int fme_mgr_write(struct fpga_manager *mgr, + const char *buf, size_t count) +{ + struct device *dev = &mgr->dev; + struct fme_mgr_priv *priv = mgr->priv; + void __iomem *fme_pr = priv->ioaddr; + u64 pr_ctrl, pr_status, pr_data; + int delay = 0, pr_credit, i = 0; + + dev_dbg(dev, "start request\n"); + + pr_ctrl = readq(fme_pr + FME_PR_CTRL); + pr_ctrl |= FME_PR_CTRL_PR_START; + writeq(pr_ctrl, fme_pr + FME_PR_CTRL); + + dev_dbg(dev, "pushing data from bitstream to HW\n"); + + /* + * driver can push data to PR hardware using PR_DATA register once HW + * has enough pr_credit (> 1), pr_credit reduces one for every 32bit + * pr data write to PR_DATA register. If pr_credit <= 1, driver needs + * to wait for enough pr_credit from hardware by polling. + */ + pr_status = readq(fme_pr + FME_PR_STS); + pr_credit = FIELD_GET(FME_PR_STS_PR_CREDIT, pr_status); + + while (count > 0) { + while (pr_credit <= 1) { + if (delay++ > PR_WAIT_TIMEOUT) { + dev_err(dev, "PR_CREDIT timeout\n"); + return -ETIMEDOUT; + } + udelay(1); + + pr_status = readq(fme_pr + FME_PR_STS); + pr_credit = FIELD_GET(FME_PR_STS_PR_CREDIT, pr_status); + } + + if (count < 4) { + dev_err(dev, "Invaild PR bitstream size\n"); + return -EINVAL; + } + + pr_data = 0; + pr_data |= FIELD_PREP(FME_PR_DATA_PR_DATA_RAW, + *(((u32 *)buf) + i)); + writeq(pr_data, fme_pr + FME_PR_DATA); + count -= 4; + pr_credit--; + i++; + } + + return 0; +} + +static int fme_mgr_write_complete(struct fpga_manager *mgr, + struct fpga_image_info *info) +{ + struct device *dev = &mgr->dev; + struct fme_mgr_priv *priv = mgr->priv; + void __iomem *fme_pr = priv->ioaddr; + u64 pr_ctrl; + + pr_ctrl = readq(fme_pr + FME_PR_CTRL); + pr_ctrl |= FME_PR_CTRL_PR_COMPLETE; + writeq(pr_ctrl, fme_pr + FME_PR_CTRL); + + dev_dbg(dev, "green bitstream push complete\n"); + dev_dbg(dev, "waiting for HW to release PR resource\n"); + + if (readq_poll_timeout(fme_pr + FME_PR_CTRL, pr_ctrl, + !(pr_ctrl & FME_PR_CTRL_PR_START), 1, + PR_WAIT_TIMEOUT)) { + dev_err(dev, "PR Completion ACK timeout.\n"); + return -ETIMEDOUT; + } + + dev_dbg(dev, "PR operation complete, checking status\n"); + priv->pr_error = fme_mgr_pr_error_handle(fme_pr); + if (priv->pr_error) { + dev_dbg(dev, "PR error detected %llx\n", + (unsigned long long)priv->pr_error); + return -EIO; + } + + dev_dbg(dev, "PR done successfully\n"); + + return 0; +} + +static enum fpga_mgr_states fme_mgr_state(struct fpga_manager *mgr) +{ + return FPGA_MGR_STATE_UNKNOWN; +} + +static u64 fme_mgr_status(struct fpga_manager *mgr) +{ + struct fme_mgr_priv *priv = mgr->priv; + + return pr_error_to_mgr_status(priv->pr_error); +} + +static const struct fpga_manager_ops fme_mgr_ops = { + .write_init = fme_mgr_write_init, + .write = fme_mgr_write, + .write_complete = fme_mgr_write_complete, + .state = fme_mgr_state, + .status = fme_mgr_status, +}; + +static int fme_mgr_probe(struct platform_device *pdev) +{ + struct dfl_fme_mgr_pdata *pdata = dev_get_platdata(&pdev->dev); + struct device *dev = &pdev->dev; + struct fme_mgr_priv *priv; + struct fpga_manager *mgr; + struct resource *res; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + if (pdata->ioaddr) + priv->ioaddr = pdata->ioaddr; + + if (!priv->ioaddr) { + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->ioaddr = devm_ioremap_resource(dev, res); + if (IS_ERR(priv->ioaddr)) + return PTR_ERR(priv->ioaddr); + } + + mgr = fpga_mgr_create(dev, "DFL FME FPGA Manager", + &fme_mgr_ops, priv); + if (!mgr) + return -ENOMEM; + + platform_set_drvdata(pdev, mgr); + + ret = fpga_mgr_register(mgr); + if (ret) + fpga_mgr_free(mgr); + + return ret; +} + +static int fme_mgr_remove(struct platform_device *pdev) +{ + struct fpga_manager *mgr = platform_get_drvdata(pdev); + + fpga_mgr_unregister(mgr); + + return 0; +} + +static struct platform_driver fme_mgr_driver = { + .driver = { + .name = DFL_FPGA_FME_MGR, + }, + .probe = fme_mgr_probe, + .remove = fme_mgr_remove, +}; + +module_platform_driver(fme_mgr_driver); + +MODULE_DESCRIPTION("FPGA Manager for DFL FPGA Management Engine"); +MODULE_AUTHOR("Intel Corporation"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:dfl-fme-mgr"); -- cgit v1.2.3 From 5ebae801d960d46e39574cb83ec24ab44d1a6c2a Mon Sep 17 00:00:00 2001 From: Wu Hao Date: Sat, 30 Jun 2018 08:53:26 +0800 Subject: fpga: dfl: fme-mgr: add compat_id support This patch adds compat_id support to fme manager driver, it reads the ID from the hardware register. And it could be used for compatibility check before partial reconfiguration. Signed-off-by: Wu Hao Acked-by: Alan Tull Acked-by: Moritz Fischer Signed-off-by: Greg Kroah-Hartman --- drivers/fpga/dfl-fme-mgr.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/drivers/fpga/dfl-fme-mgr.c b/drivers/fpga/dfl-fme-mgr.c index df843b51c663..b5ef405b6d88 100644 --- a/drivers/fpga/dfl-fme-mgr.c +++ b/drivers/fpga/dfl-fme-mgr.c @@ -272,9 +272,17 @@ static const struct fpga_manager_ops fme_mgr_ops = { .status = fme_mgr_status, }; +static void fme_mgr_get_compat_id(void __iomem *fme_pr, + struct fpga_compat_id *id) +{ + id->id_l = readq(fme_pr + FME_PR_INTFC_ID_L); + id->id_h = readq(fme_pr + FME_PR_INTFC_ID_H); +} + static int fme_mgr_probe(struct platform_device *pdev) { struct dfl_fme_mgr_pdata *pdata = dev_get_platdata(&pdev->dev); + struct fpga_compat_id *compat_id; struct device *dev = &pdev->dev; struct fme_mgr_priv *priv; struct fpga_manager *mgr; @@ -295,11 +303,18 @@ static int fme_mgr_probe(struct platform_device *pdev) return PTR_ERR(priv->ioaddr); } + compat_id = devm_kzalloc(dev, sizeof(*compat_id), GFP_KERNEL); + if (!compat_id) + return -ENOMEM; + + fme_mgr_get_compat_id(priv->ioaddr, compat_id); + mgr = fpga_mgr_create(dev, "DFL FME FPGA Manager", &fme_mgr_ops, priv); if (!mgr) return -ENOMEM; + mgr->compat_id = compat_id; platform_set_drvdata(pdev, mgr); ret = fpga_mgr_register(mgr); -- cgit v1.2.3 From de892dff17b36d138ff41aeb46366d7c1ed4cd77 Mon Sep 17 00:00:00 2001 From: Wu Hao Date: Sat, 30 Jun 2018 08:53:27 +0800 Subject: fpga: dfl: add fpga bridge platform driver for FME This patch adds fpga bridge platform driver for FPGA Management Engine. It implements the enable_set callback for fpga bridge. Signed-off-by: Tim Whisonant Signed-off-by: Enno Luebbers Signed-off-by: Shiva Rao Signed-off-by: Christopher Rauer Signed-off-by: Wu Hao Acked-by: Alan Tull Acked-by: Moritz Fischer Signed-off-by: Greg Kroah-Hartman --- drivers/fpga/Kconfig | 6 +++ drivers/fpga/Makefile | 1 + drivers/fpga/dfl-fme-br.c | 114 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 121 insertions(+) create mode 100644 drivers/fpga/dfl-fme-br.c diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig index 46b48e5164db..11d1943b91d4 100644 --- a/drivers/fpga/Kconfig +++ b/drivers/fpga/Kconfig @@ -162,6 +162,12 @@ config FPGA_DFL_FME_MGR help Say Y to enable FPGA Manager driver for FPGA Management Engine. +config FPGA_DFL_FME_BRIDGE + tristate "FPGA DFL FME Bridge Driver" + depends on FPGA_DFL_FME && HAS_IOMEM + help + Say Y to enable FPGA Bridge driver for FPGA Management Engine. + config FPGA_DFL_PCI tristate "FPGA DFL PCIe Device Driver" depends on PCI && FPGA_DFL diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile index 23f41b02f894..cda05515ebfd 100644 --- a/drivers/fpga/Makefile +++ b/drivers/fpga/Makefile @@ -33,6 +33,7 @@ obj-$(CONFIG_OF_FPGA_REGION) += of-fpga-region.o obj-$(CONFIG_FPGA_DFL) += dfl.o obj-$(CONFIG_FPGA_DFL_FME) += dfl-fme.o obj-$(CONFIG_FPGA_DFL_FME_MGR) += dfl-fme-mgr.o +obj-$(CONFIG_FPGA_DFL_FME_BRIDGE) += dfl-fme-br.o dfl-fme-objs := dfl-fme-main.o dfl-fme-pr.o diff --git a/drivers/fpga/dfl-fme-br.c b/drivers/fpga/dfl-fme-br.c new file mode 100644 index 000000000000..7cc041def8b3 --- /dev/null +++ b/drivers/fpga/dfl-fme-br.c @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * FPGA Bridge Driver for FPGA Management Engine (FME) + * + * Copyright (C) 2017-2018 Intel Corporation, Inc. + * + * Authors: + * Wu Hao + * Joseph Grecco + * Enno Luebbers + * Tim Whisonant + * Ananda Ravuri + * Henry Mitchel + */ + +#include +#include + +#include "dfl.h" +#include "dfl-fme-pr.h" + +struct fme_br_priv { + struct dfl_fme_br_pdata *pdata; + struct dfl_fpga_port_ops *port_ops; + struct platform_device *port_pdev; +}; + +static int fme_bridge_enable_set(struct fpga_bridge *bridge, bool enable) +{ + struct fme_br_priv *priv = bridge->priv; + struct platform_device *port_pdev; + struct dfl_fpga_port_ops *ops; + + if (!priv->port_pdev) { + port_pdev = dfl_fpga_cdev_find_port(priv->pdata->cdev, + &priv->pdata->port_id, + dfl_fpga_check_port_id); + if (!port_pdev) + return -ENODEV; + + priv->port_pdev = port_pdev; + } + + if (priv->port_pdev && !priv->port_ops) { + ops = dfl_fpga_port_ops_get(priv->port_pdev); + if (!ops || !ops->enable_set) + return -ENOENT; + + priv->port_ops = ops; + } + + return priv->port_ops->enable_set(priv->port_pdev, enable); +} + +static const struct fpga_bridge_ops fme_bridge_ops = { + .enable_set = fme_bridge_enable_set, +}; + +static int fme_br_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct fme_br_priv *priv; + struct fpga_bridge *br; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->pdata = dev_get_platdata(dev); + + br = fpga_bridge_create(dev, "DFL FPGA FME Bridge", + &fme_bridge_ops, priv); + if (!br) + return -ENOMEM; + + platform_set_drvdata(pdev, br); + + ret = fpga_bridge_register(br); + if (ret) + fpga_bridge_free(br); + + return ret; +} + +static int fme_br_remove(struct platform_device *pdev) +{ + struct fpga_bridge *br = platform_get_drvdata(pdev); + struct fme_br_priv *priv = br->priv; + + fpga_bridge_unregister(br); + + if (priv->port_pdev) + put_device(&priv->port_pdev->dev); + if (priv->port_ops) + dfl_fpga_port_ops_put(priv->port_ops); + + return 0; +} + +static struct platform_driver fme_br_driver = { + .driver = { + .name = DFL_FPGA_FME_BRIDGE, + }, + .probe = fme_br_probe, + .remove = fme_br_remove, +}; + +module_platform_driver(fme_br_driver); + +MODULE_DESCRIPTION("FPGA Bridge for DFL FPGA Management Engine"); +MODULE_AUTHOR("Intel Corporation"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:dfl-fme-bridge"); -- cgit v1.2.3 From bb61b9be3e6b001f1571b230316bf3867dc41df3 Mon Sep 17 00:00:00 2001 From: Wu Hao Date: Sat, 30 Jun 2018 08:53:28 +0800 Subject: fpga: dfl: add fpga region platform driver for FME This patch adds fpga region platform driver for FPGA Management Engine. It register an fpga region with given fpga manager / bridge device. Signed-off-by: Tim Whisonant Signed-off-by: Enno Luebbers Signed-off-by: Shiva Rao Signed-off-by: Christopher Rauer Signed-off-by: Wu Hao Acked-by: Alan Tull Acked-by: Moritz Fischer Signed-off-by: Greg Kroah-Hartman --- drivers/fpga/Kconfig | 6 +++ drivers/fpga/Makefile | 1 + drivers/fpga/dfl-fme-region.c | 88 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+) create mode 100644 drivers/fpga/dfl-fme-region.c diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig index 11d1943b91d4..f99f422feec9 100644 --- a/drivers/fpga/Kconfig +++ b/drivers/fpga/Kconfig @@ -168,6 +168,12 @@ config FPGA_DFL_FME_BRIDGE help Say Y to enable FPGA Bridge driver for FPGA Management Engine. +config FPGA_DFL_FME_REGION + tristate "FPGA DFL FME Region Driver" + depends on FPGA_DFL_FME && HAS_IOMEM + help + Say Y to enable FPGA Region driver for FPGA Management Engine. + config FPGA_DFL_PCI tristate "FPGA DFL PCIe Device Driver" depends on PCI && FPGA_DFL diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile index cda05515ebfd..637c27512c28 100644 --- a/drivers/fpga/Makefile +++ b/drivers/fpga/Makefile @@ -34,6 +34,7 @@ obj-$(CONFIG_FPGA_DFL) += dfl.o obj-$(CONFIG_FPGA_DFL_FME) += dfl-fme.o obj-$(CONFIG_FPGA_DFL_FME_MGR) += dfl-fme-mgr.o obj-$(CONFIG_FPGA_DFL_FME_BRIDGE) += dfl-fme-br.o +obj-$(CONFIG_FPGA_DFL_FME_REGION) += dfl-fme-region.o dfl-fme-objs := dfl-fme-main.o dfl-fme-pr.o diff --git a/drivers/fpga/dfl-fme-region.c b/drivers/fpga/dfl-fme-region.c new file mode 100644 index 000000000000..a6e0bde7cc31 --- /dev/null +++ b/drivers/fpga/dfl-fme-region.c @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * FPGA Region Driver for FPGA Management Engine (FME) + * + * Copyright (C) 2017-2018 Intel Corporation, Inc. + * + * Authors: + * Wu Hao + * Joseph Grecco + * Enno Luebbers + * Tim Whisonant + * Ananda Ravuri + * Henry Mitchel + */ + +#include +#include + +#include "dfl-fme-pr.h" + +static int fme_region_get_bridges(struct fpga_region *region) +{ + struct dfl_fme_region_pdata *pdata = region->priv; + struct device *dev = &pdata->br->dev; + + return fpga_bridge_get_to_list(dev, region->info, ®ion->bridge_list); +} + +static int fme_region_probe(struct platform_device *pdev) +{ + struct dfl_fme_region_pdata *pdata = dev_get_platdata(&pdev->dev); + struct device *dev = &pdev->dev; + struct fpga_region *region; + struct fpga_manager *mgr; + int ret; + + mgr = fpga_mgr_get(&pdata->mgr->dev); + if (IS_ERR(mgr)) + return -EPROBE_DEFER; + + region = fpga_region_create(dev, mgr, fme_region_get_bridges); + if (!region) { + ret = -ENOMEM; + goto eprobe_mgr_put; + } + + region->priv = pdata; + platform_set_drvdata(pdev, region); + + ret = fpga_region_register(region); + if (ret) + goto region_free; + + dev_dbg(dev, "DFL FME FPGA Region probed\n"); + + return 0; + +region_free: + fpga_region_free(region); +eprobe_mgr_put: + fpga_mgr_put(mgr); + return ret; +} + +static int fme_region_remove(struct platform_device *pdev) +{ + struct fpga_region *region = dev_get_drvdata(&pdev->dev); + + fpga_region_unregister(region); + fpga_mgr_put(region->mgr); + + return 0; +} + +static struct platform_driver fme_region_driver = { + .driver = { + .name = DFL_FPGA_FME_REGION, + }, + .probe = fme_region_probe, + .remove = fme_region_remove, +}; + +module_platform_driver(fme_region_driver); + +MODULE_DESCRIPTION("FPGA Region for DFL FPGA Management Engine"); +MODULE_AUTHOR("Intel Corporation"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:dfl-fme-region"); -- cgit v1.2.3 From 7514a4249c479c0b56a112306496a9aa3bad312b Mon Sep 17 00:00:00 2001 From: Wu Hao Date: Sat, 30 Jun 2018 08:53:29 +0800 Subject: fpga: dfl: fme-region: add support for compat_id This patch adds compat_id support, it reuses fme manager's compat id, as the per region compat id is actually from the fme manager's register. Signed-off-by: Wu Hao Acked-by: Alan Tull Acked-by: Moritz Fischer Signed-off-by: Greg Kroah-Hartman --- drivers/fpga/dfl-fme-region.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/fpga/dfl-fme-region.c b/drivers/fpga/dfl-fme-region.c index a6e0bde7cc31..0b7e19c27c6d 100644 --- a/drivers/fpga/dfl-fme-region.c +++ b/drivers/fpga/dfl-fme-region.c @@ -45,6 +45,7 @@ static int fme_region_probe(struct platform_device *pdev) } region->priv = pdata; + region->compat_id = mgr->compat_id; platform_set_drvdata(pdev, region); ret = fpga_region_register(region); -- cgit v1.2.3 From 1a1527cf5ddacc6716a3cacfa232111d92ffd93b Mon Sep 17 00:00:00 2001 From: Wu Hao Date: Sat, 30 Jun 2018 08:53:30 +0800 Subject: fpga: dfl: add FPGA Accelerated Function Unit driver basic framework On DFL FPGA devices, the Accelerated Function Unit (AFU), can be reprogrammed for different functions. It connects to the FPGA infrastructure (static FPGA region) via a Port. Port CSRs are implemented separately from the AFU CSRs to provide control and status of the Port. Once valid PR bitstream is programmed into the AFU, it allows access to the AFU CSRs in the AFU MMIO space. This patch only implements basic driver framework for AFU, including device file operation framework. Signed-off-by: Tim Whisonant Signed-off-by: Enno Luebbers Signed-off-by: Shiva Rao Signed-off-by: Christopher Rauer Signed-off-by: Xiao Guangrong Signed-off-by: Wu Hao Acked-by: Alan Tull Signed-off-by: Greg Kroah-Hartman --- drivers/fpga/Kconfig | 9 +++ drivers/fpga/Makefile | 2 + drivers/fpga/dfl-afu-main.c | 162 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 173 insertions(+) create mode 100644 drivers/fpga/dfl-afu-main.c diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig index f99f422feec9..1ebcef4bab5b 100644 --- a/drivers/fpga/Kconfig +++ b/drivers/fpga/Kconfig @@ -174,6 +174,15 @@ config FPGA_DFL_FME_REGION help Say Y to enable FPGA Region driver for FPGA Management Engine. +config FPGA_DFL_AFU + tristate "FPGA DFL AFU Driver" + depends on FPGA_DFL + help + This is the driver for FPGA Accelerated Function Unit (AFU) which + implements AFU and Port management features. A User AFU connects + to the FPGA infrastructure via a Port. There may be more than one + Port/AFU per DFL based FPGA device. + config FPGA_DFL_PCI tristate "FPGA DFL PCIe Device Driver" depends on PCI && FPGA_DFL diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile index 637c27512c28..1ac7749b2542 100644 --- a/drivers/fpga/Makefile +++ b/drivers/fpga/Makefile @@ -35,8 +35,10 @@ obj-$(CONFIG_FPGA_DFL_FME) += dfl-fme.o obj-$(CONFIG_FPGA_DFL_FME_MGR) += dfl-fme-mgr.o obj-$(CONFIG_FPGA_DFL_FME_BRIDGE) += dfl-fme-br.o obj-$(CONFIG_FPGA_DFL_FME_REGION) += dfl-fme-region.o +obj-$(CONFIG_FPGA_DFL_AFU) += dfl-afu.o dfl-fme-objs := dfl-fme-main.o dfl-fme-pr.o +dfl-afu-objs := dfl-afu-main.o # Drivers for FPGAs which implement DFL obj-$(CONFIG_FPGA_DFL_PCI) += dfl-pci.o diff --git a/drivers/fpga/dfl-afu-main.c b/drivers/fpga/dfl-afu-main.c new file mode 100644 index 000000000000..08f88cdb3bfc --- /dev/null +++ b/drivers/fpga/dfl-afu-main.c @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for FPGA Accelerated Function Unit (AFU) + * + * Copyright (C) 2017-2018 Intel Corporation, Inc. + * + * Authors: + * Wu Hao + * Xiao Guangrong + * Joseph Grecco + * Enno Luebbers + * Tim Whisonant + * Ananda Ravuri + * Henry Mitchel + */ + +#include +#include + +#include "dfl.h" + +static int port_hdr_init(struct platform_device *pdev, + struct dfl_feature *feature) +{ + dev_dbg(&pdev->dev, "PORT HDR Init.\n"); + + return 0; +} + +static void port_hdr_uinit(struct platform_device *pdev, + struct dfl_feature *feature) +{ + dev_dbg(&pdev->dev, "PORT HDR UInit.\n"); +} + +static const struct dfl_feature_ops port_hdr_ops = { + .init = port_hdr_init, + .uinit = port_hdr_uinit, +}; + +static struct dfl_feature_driver port_feature_drvs[] = { + { + .id = PORT_FEATURE_ID_HEADER, + .ops = &port_hdr_ops, + }, + { + .ops = NULL, + } +}; + +static int afu_open(struct inode *inode, struct file *filp) +{ + struct platform_device *fdev = dfl_fpga_inode_to_feature_dev(inode); + struct dfl_feature_platform_data *pdata; + int ret; + + pdata = dev_get_platdata(&fdev->dev); + if (WARN_ON(!pdata)) + return -ENODEV; + + ret = dfl_feature_dev_use_begin(pdata); + if (ret) + return ret; + + dev_dbg(&fdev->dev, "Device File Open\n"); + filp->private_data = fdev; + + return 0; +} + +static int afu_release(struct inode *inode, struct file *filp) +{ + struct platform_device *pdev = filp->private_data; + struct dfl_feature_platform_data *pdata; + + dev_dbg(&pdev->dev, "Device File Release\n"); + + pdata = dev_get_platdata(&pdev->dev); + + dfl_feature_dev_use_end(pdata); + + return 0; +} + +static long afu_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + struct platform_device *pdev = filp->private_data; + struct dfl_feature_platform_data *pdata; + struct dfl_feature *f; + long ret; + + dev_dbg(&pdev->dev, "%s cmd 0x%x\n", __func__, cmd); + + pdata = dev_get_platdata(&pdev->dev); + + switch (cmd) { + default: + /* + * Let sub-feature's ioctl function to handle the cmd + * Sub-feature's ioctl returns -ENODEV when cmd is not + * handled in this sub feature, and returns 0 and other + * error code if cmd is handled. + */ + dfl_fpga_dev_for_each_feature(pdata, f) + if (f->ops && f->ops->ioctl) { + ret = f->ops->ioctl(pdev, f, cmd, arg); + if (ret != -ENODEV) + return ret; + } + } + + return -EINVAL; +} + +static const struct file_operations afu_fops = { + .owner = THIS_MODULE, + .open = afu_open, + .release = afu_release, + .unlocked_ioctl = afu_ioctl, +}; + +static int afu_probe(struct platform_device *pdev) +{ + int ret; + + dev_dbg(&pdev->dev, "%s\n", __func__); + + ret = dfl_fpga_dev_feature_init(pdev, port_feature_drvs); + if (ret) + return ret; + + ret = dfl_fpga_dev_ops_register(pdev, &afu_fops, THIS_MODULE); + if (ret) + dfl_fpga_dev_feature_uinit(pdev); + + return ret; +} + +static int afu_remove(struct platform_device *pdev) +{ + dev_dbg(&pdev->dev, "%s\n", __func__); + + dfl_fpga_dev_ops_unregister(pdev); + dfl_fpga_dev_feature_uinit(pdev); + + return 0; +} + +static struct platform_driver afu_driver = { + .driver = { + .name = DFL_FPGA_FEATURE_DEV_PORT, + }, + .probe = afu_probe, + .remove = afu_remove, +}; + +module_platform_driver(afu_driver); + +MODULE_DESCRIPTION("FPGA Accelerated Function Unit driver"); +MODULE_AUTHOR("Intel Corporation"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:dfl-port"); -- cgit v1.2.3 From 47c1b19c160fe1641469c145dba78fbbe48b996a Mon Sep 17 00:00:00 2001 From: Wu Hao Date: Sat, 30 Jun 2018 08:53:31 +0800 Subject: fpga: dfl: afu: add port ops support This patch registers the port ops into the global list in the DFL framework, and it allows other modules to use the port ops. And This patch includes the implementation of the get_id and enable_set ops too. Signed-off-by: Wu Hao Acked-by: Alan Tull Signed-off-by: Greg Kroah-Hartman --- drivers/fpga/dfl-afu-main.c | 122 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 121 insertions(+), 1 deletion(-) diff --git a/drivers/fpga/dfl-afu-main.c b/drivers/fpga/dfl-afu-main.c index 08f88cdb3bfc..a38d6a825e7e 100644 --- a/drivers/fpga/dfl-afu-main.c +++ b/drivers/fpga/dfl-afu-main.c @@ -19,6 +19,83 @@ #include "dfl.h" +/** + * port_enable - enable a port + * @pdev: port platform device. + * + * Enable Port by clear the port soft reset bit, which is set by default. + * The User AFU is unable to respond to any MMIO access while in reset. + * port_enable function should only be used after port_disable + * function. + */ +static void port_enable(struct platform_device *pdev) +{ + struct dfl_feature_platform_data *pdata = dev_get_platdata(&pdev->dev); + void __iomem *base; + u64 v; + + WARN_ON(!pdata->disable_count); + + if (--pdata->disable_count != 0) + return; + + base = dfl_get_feature_ioaddr_by_id(&pdev->dev, PORT_FEATURE_ID_HEADER); + + /* Clear port soft reset */ + v = readq(base + PORT_HDR_CTRL); + v &= ~PORT_CTRL_SFTRST; + writeq(v, base + PORT_HDR_CTRL); +} + +#define RST_POLL_INVL 10 /* us */ +#define RST_POLL_TIMEOUT 1000 /* us */ + +/** + * port_disable - disable a port + * @pdev: port platform device. + * + * Disable Port by setting the port soft reset bit, it puts the port into + * reset. + */ +static int port_disable(struct platform_device *pdev) +{ + struct dfl_feature_platform_data *pdata = dev_get_platdata(&pdev->dev); + void __iomem *base; + u64 v; + + if (pdata->disable_count++ != 0) + return 0; + + base = dfl_get_feature_ioaddr_by_id(&pdev->dev, PORT_FEATURE_ID_HEADER); + + /* Set port soft reset */ + v = readq(base + PORT_HDR_CTRL); + v |= PORT_CTRL_SFTRST; + writeq(v, base + PORT_HDR_CTRL); + + /* + * HW sets ack bit to 1 when all outstanding requests have been drained + * on this port and minimum soft reset pulse width has elapsed. + * Driver polls port_soft_reset_ack to determine if reset done by HW. + */ + if (readq_poll_timeout(base + PORT_HDR_CTRL, v, v & PORT_CTRL_SFTRST, + RST_POLL_INVL, RST_POLL_TIMEOUT)) { + dev_err(&pdev->dev, "timeout, fail to reset device\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static int port_get_id(struct platform_device *pdev) +{ + void __iomem *base; + + base = dfl_get_feature_ioaddr_by_id(&pdev->dev, PORT_FEATURE_ID_HEADER); + + return FIELD_GET(PORT_CAP_PORT_NUM, readq(base + PORT_HDR_CAP)); +} + static int port_hdr_init(struct platform_device *pdev, struct dfl_feature *feature) { @@ -119,6 +196,28 @@ static const struct file_operations afu_fops = { .unlocked_ioctl = afu_ioctl, }; +static int port_enable_set(struct platform_device *pdev, bool enable) +{ + struct dfl_feature_platform_data *pdata = dev_get_platdata(&pdev->dev); + int ret = 0; + + mutex_lock(&pdata->lock); + if (enable) + port_enable(pdev); + else + ret = port_disable(pdev); + mutex_unlock(&pdata->lock); + + return ret; +} + +static struct dfl_fpga_port_ops afu_port_ops = { + .name = DFL_FPGA_FEATURE_DEV_PORT, + .owner = THIS_MODULE, + .get_id = port_get_id, + .enable_set = port_enable_set, +}; + static int afu_probe(struct platform_device *pdev) { int ret; @@ -154,7 +253,28 @@ static struct platform_driver afu_driver = { .remove = afu_remove, }; -module_platform_driver(afu_driver); +static int __init afu_init(void) +{ + int ret; + + dfl_fpga_port_ops_add(&afu_port_ops); + + ret = platform_driver_register(&afu_driver); + if (ret) + dfl_fpga_port_ops_del(&afu_port_ops); + + return ret; +} + +static void __exit afu_exit(void) +{ + platform_driver_unregister(&afu_driver); + + dfl_fpga_port_ops_del(&afu_port_ops); +} + +module_init(afu_init); +module_exit(afu_exit); MODULE_DESCRIPTION("FPGA Accelerated Function Unit driver"); MODULE_AUTHOR("Intel Corporation"); -- cgit v1.2.3 From e4664c0ee4ac44993c62d10b048ab0a960691da5 Mon Sep 17 00:00:00 2001 From: Wu Hao Date: Sat, 30 Jun 2018 08:53:32 +0800 Subject: fpga: dfl: afu: add header sub feature support The port header register set is always present for port, it is mainly for capability, control and status of the ports that AFU connected to. This patch implements header sub feature support. Below user interfaces are created by this patch. Sysfs interface: * /sys/class/fpga_region///id Read-only. Port ID. Ioctl interface: * DFL_FPGA_PORT_RESET Reset the FPGA Port and its AFU. Signed-off-by: Tim Whisonant Signed-off-by: Enno Luebbers Signed-off-by: Shiva Rao Signed-off-by: Christopher Rauer Signed-off-by: Xiao Guangrong Signed-off-by: Wu Hao Acked-by: Alan Tull Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-platform-dfl-port | 7 ++ drivers/fpga/dfl-afu-main.c | 79 ++++++++++++++++++++++- include/uapi/linux/fpga-dfl.h | 17 +++++ 3 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 Documentation/ABI/testing/sysfs-platform-dfl-port diff --git a/Documentation/ABI/testing/sysfs-platform-dfl-port b/Documentation/ABI/testing/sysfs-platform-dfl-port new file mode 100644 index 000000000000..cb91165f5397 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-platform-dfl-port @@ -0,0 +1,7 @@ +What: /sys/bus/platform/devices/dfl-port.0/id +Date: June 2018 +KernelVersion: 4.19 +Contact: Wu Hao +Description: Read-only. It returns id of this port. One DFL FPGA device + may have more than one port. Userspace could use this id to + distinguish different ports under same FPGA device. diff --git a/drivers/fpga/dfl-afu-main.c b/drivers/fpga/dfl-afu-main.c index a38d6a825e7e..d36b3e9f3984 100644 --- a/drivers/fpga/dfl-afu-main.c +++ b/drivers/fpga/dfl-afu-main.c @@ -16,6 +16,7 @@ #include #include +#include #include "dfl.h" @@ -87,6 +88,41 @@ static int port_disable(struct platform_device *pdev) return 0; } +/* + * This function resets the FPGA Port and its accelerator (AFU) by function + * __port_disable and __port_enable (set port soft reset bit and then clear + * it). Userspace can do Port reset at any time, e.g. during DMA or Partial + * Reconfiguration. But it should never cause any system level issue, only + * functional failure (e.g. DMA or PR operation failure) and be recoverable + * from the failure. + * + * Note: the accelerator (AFU) is not accessible when its port is in reset + * (disabled). Any attempts on MMIO access to AFU while in reset, will + * result errors reported via port error reporting sub feature (if present). + */ +static int __port_reset(struct platform_device *pdev) +{ + int ret; + + ret = port_disable(pdev); + if (!ret) + port_enable(pdev); + + return ret; +} + +static int port_reset(struct platform_device *pdev) +{ + struct dfl_feature_platform_data *pdata = dev_get_platdata(&pdev->dev); + int ret; + + mutex_lock(&pdata->lock); + ret = __port_reset(pdev); + mutex_unlock(&pdata->lock); + + return ret; +} + static int port_get_id(struct platform_device *pdev) { void __iomem *base; @@ -96,23 +132,63 @@ static int port_get_id(struct platform_device *pdev) return FIELD_GET(PORT_CAP_PORT_NUM, readq(base + PORT_HDR_CAP)); } +static ssize_t +id_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + int id = port_get_id(to_platform_device(dev)); + + return scnprintf(buf, PAGE_SIZE, "%d\n", id); +} +static DEVICE_ATTR_RO(id); + +static const struct attribute *port_hdr_attrs[] = { + &dev_attr_id.attr, + NULL, +}; + static int port_hdr_init(struct platform_device *pdev, struct dfl_feature *feature) { dev_dbg(&pdev->dev, "PORT HDR Init.\n"); - return 0; + port_reset(pdev); + + return sysfs_create_files(&pdev->dev.kobj, port_hdr_attrs); } static void port_hdr_uinit(struct platform_device *pdev, struct dfl_feature *feature) { dev_dbg(&pdev->dev, "PORT HDR UInit.\n"); + + sysfs_remove_files(&pdev->dev.kobj, port_hdr_attrs); +} + +static long +port_hdr_ioctl(struct platform_device *pdev, struct dfl_feature *feature, + unsigned int cmd, unsigned long arg) +{ + long ret; + + switch (cmd) { + case DFL_FPGA_PORT_RESET: + if (!arg) + ret = port_reset(pdev); + else + ret = -EINVAL; + break; + default: + dev_dbg(&pdev->dev, "%x cmd not handled", cmd); + ret = -ENODEV; + } + + return ret; } static const struct dfl_feature_ops port_hdr_ops = { .init = port_hdr_init, .uinit = port_hdr_uinit, + .ioctl = port_hdr_ioctl, }; static struct dfl_feature_driver port_feature_drvs[] = { @@ -154,6 +230,7 @@ static int afu_release(struct inode *inode, struct file *filp) pdata = dev_get_platdata(&pdev->dev); + port_reset(pdev); dfl_feature_dev_use_end(pdata); return 0; diff --git a/include/uapi/linux/fpga-dfl.h b/include/uapi/linux/fpga-dfl.h index 9666af85a8f5..e6b4dd26cc68 100644 --- a/include/uapi/linux/fpga-dfl.h +++ b/include/uapi/linux/fpga-dfl.h @@ -29,8 +29,11 @@ #define DFL_FPGA_MAGIC 0xB6 #define DFL_FPGA_BASE 0 +#define DFL_PORT_BASE 0x40 #define DFL_FME_BASE 0x80 +/* Common IOCTLs for both FME and AFU file descriptor */ + /** * DFL_FPGA_GET_API_VERSION - _IO(DFL_FPGA_MAGIC, DFL_FPGA_BASE + 0) * @@ -49,6 +52,20 @@ #define DFL_FPGA_CHECK_EXTENSION _IO(DFL_FPGA_MAGIC, DFL_FPGA_BASE + 1) +/* IOCTLs for AFU file descriptor */ + +/** + * DFL_FPGA_PORT_RESET - _IO(DFL_FPGA_MAGIC, DFL_PORT_BASE + 0) + * + * Reset the FPGA Port and its AFU. No parameters are supported. + * Userspace can do Port reset at any time, e.g. during DMA or PR. But + * it should never cause any system level issue, only functional failure + * (e.g. DMA or PR operation failure) and be recoverable from the failure. + * Return: 0 on success, -errno of failure + */ + +#define DFL_FPGA_PORT_RESET _IO(DFL_FPGA_MAGIC, DFL_PORT_BASE + 0) + /* IOCTLs for FME file descriptor */ /** -- cgit v1.2.3 From 6fd893c409e5939ea2145b27796c155535988734 Mon Sep 17 00:00:00 2001 From: Wu Hao Date: Sat, 30 Jun 2018 08:53:33 +0800 Subject: fpga: dfl: afu: add DFL_FPGA_GET_API_VERSION/CHECK_EXTENSION ioctls support DFL_FPGA_GET_API_VERSION and DFL_FPGA_CHECK_EXTENSION ioctls are common ones which need to be supported by all feature devices drivers including FME and AFU. This patch implements above 2 ioctls in FPGA Accelerated Function Unit (AFU) driver. Signed-off-by: Tim Whisonant Signed-off-by: Enno Luebbers Signed-off-by: Shiva Rao Signed-off-by: Christopher Rauer Signed-off-by: Xiao Guangrong Signed-off-by: Wu Hao Acked-by: Alan Tull Acked-by: Moritz Fischer Signed-off-by: Greg Kroah-Hartman --- drivers/fpga/dfl-afu-main.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/fpga/dfl-afu-main.c b/drivers/fpga/dfl-afu-main.c index d36b3e9f3984..4074b97122e2 100644 --- a/drivers/fpga/dfl-afu-main.c +++ b/drivers/fpga/dfl-afu-main.c @@ -236,6 +236,13 @@ static int afu_release(struct inode *inode, struct file *filp) return 0; } +static long afu_ioctl_check_extension(struct dfl_feature_platform_data *pdata, + unsigned long arg) +{ + /* No extension support for now */ + return 0; +} + static long afu_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { struct platform_device *pdev = filp->private_data; @@ -248,6 +255,10 @@ static long afu_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) pdata = dev_get_platdata(&pdev->dev); switch (cmd) { + case DFL_FPGA_GET_API_VERSION: + return DFL_FPGA_API_VERSION; + case DFL_FPGA_CHECK_EXTENSION: + return afu_ioctl_check_extension(pdata, arg); default: /* * Let sub-feature's ioctl function to handle the cmd -- cgit v1.2.3 From 857a26222ff75eecf7d701ef0e91e4fbf6efa663 Mon Sep 17 00:00:00 2001 From: Xiao Guangrong Date: Sat, 30 Jun 2018 08:53:34 +0800 Subject: fpga: dfl: afu: add afu sub feature support User Accelerated Function Unit sub feature exposes the MMIO region of the AFU. After valid PR bitstream is programmed and the port is enabled, then this MMIO region could be accessed. This patch adds support to enumerate the AFU MMIO region and expose it to userspace via mmap file operation. Below interfaces are exposed to user: Sysfs interface: * /sys/class/fpga_region///afu_id Read-only. Indicate which PR bitstream is programmed to this AFU. Ioctl interfaces: * DFL_FPGA_PORT_GET_INFO Provide info to userspace on the number of supported region. Only UAFU region is supported now. * DFL_FPGA_PORT_GET_REGION_INFO Provide region information, including access permission, region size, offset from the start of device fd. Signed-off-by: Tim Whisonant Signed-off-by: Enno Luebbers Signed-off-by: Shiva Rao Signed-off-by: Christopher Rauer Signed-off-by: Xiao Guangrong Signed-off-by: Wu Hao Acked-by: Alan Tull Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-platform-dfl-port | 9 + drivers/fpga/Makefile | 2 +- drivers/fpga/dfl-afu-main.c | 219 +++++++++++++++++++++- drivers/fpga/dfl-afu-region.c | 166 ++++++++++++++++ drivers/fpga/dfl-afu.h | 71 +++++++ include/uapi/linux/fpga-dfl.h | 48 +++++ 6 files changed, 508 insertions(+), 7 deletions(-) create mode 100644 drivers/fpga/dfl-afu-region.c create mode 100644 drivers/fpga/dfl-afu.h diff --git a/Documentation/ABI/testing/sysfs-platform-dfl-port b/Documentation/ABI/testing/sysfs-platform-dfl-port index cb91165f5397..6a92dda517b0 100644 --- a/Documentation/ABI/testing/sysfs-platform-dfl-port +++ b/Documentation/ABI/testing/sysfs-platform-dfl-port @@ -5,3 +5,12 @@ Contact: Wu Hao Description: Read-only. It returns id of this port. One DFL FPGA device may have more than one port. Userspace could use this id to distinguish different ports under same FPGA device. + +What: /sys/bus/platform/devices/dfl-port.0/afu_id +Date: June 2018 +KernelVersion: 4.19 +Contact: Wu Hao +Description: Read-only. User can program different PR bitstreams to FPGA + Accelerator Function Unit (AFU) for different functions. It + returns uuid which could be used to identify which PR bitstream + is programmed in this AFU. diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile index 1ac7749b2542..a44d50dd0b70 100644 --- a/drivers/fpga/Makefile +++ b/drivers/fpga/Makefile @@ -38,7 +38,7 @@ obj-$(CONFIG_FPGA_DFL_FME_REGION) += dfl-fme-region.o obj-$(CONFIG_FPGA_DFL_AFU) += dfl-afu.o dfl-fme-objs := dfl-fme-main.o dfl-fme-pr.o -dfl-afu-objs := dfl-afu-main.o +dfl-afu-objs := dfl-afu-main.o dfl-afu-region.o # Drivers for FPGAs which implement DFL obj-$(CONFIG_FPGA_DFL_PCI) += dfl-pci.o diff --git a/drivers/fpga/dfl-afu-main.c b/drivers/fpga/dfl-afu-main.c index 4074b97122e2..f67a78d7e9ad 100644 --- a/drivers/fpga/dfl-afu-main.c +++ b/drivers/fpga/dfl-afu-main.c @@ -16,18 +16,18 @@ #include #include +#include #include -#include "dfl.h" +#include "dfl-afu.h" /** * port_enable - enable a port * @pdev: port platform device. * * Enable Port by clear the port soft reset bit, which is set by default. - * The User AFU is unable to respond to any MMIO access while in reset. - * port_enable function should only be used after port_disable - * function. + * The AFU is unable to respond to any MMIO access while in reset. + * port_enable function should only be used after port_disable function. */ static void port_enable(struct platform_device *pdev) { @@ -191,11 +191,74 @@ static const struct dfl_feature_ops port_hdr_ops = { .ioctl = port_hdr_ioctl, }; +static ssize_t +afu_id_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct dfl_feature_platform_data *pdata = dev_get_platdata(dev); + void __iomem *base; + u64 guidl, guidh; + + base = dfl_get_feature_ioaddr_by_id(dev, PORT_FEATURE_ID_AFU); + + mutex_lock(&pdata->lock); + if (pdata->disable_count) { + mutex_unlock(&pdata->lock); + return -EBUSY; + } + + guidl = readq(base + GUID_L); + guidh = readq(base + GUID_H); + mutex_unlock(&pdata->lock); + + return scnprintf(buf, PAGE_SIZE, "%016llx%016llx\n", guidh, guidl); +} +static DEVICE_ATTR_RO(afu_id); + +static const struct attribute *port_afu_attrs[] = { + &dev_attr_afu_id.attr, + NULL +}; + +static int port_afu_init(struct platform_device *pdev, + struct dfl_feature *feature) +{ + struct resource *res = &pdev->resource[feature->resource_index]; + int ret; + + dev_dbg(&pdev->dev, "PORT AFU Init.\n"); + + ret = afu_mmio_region_add(dev_get_platdata(&pdev->dev), + DFL_PORT_REGION_INDEX_AFU, resource_size(res), + res->start, DFL_PORT_REGION_READ | + DFL_PORT_REGION_WRITE | DFL_PORT_REGION_MMAP); + if (ret) + return ret; + + return sysfs_create_files(&pdev->dev.kobj, port_afu_attrs); +} + +static void port_afu_uinit(struct platform_device *pdev, + struct dfl_feature *feature) +{ + dev_dbg(&pdev->dev, "PORT AFU UInit.\n"); + + sysfs_remove_files(&pdev->dev.kobj, port_afu_attrs); +} + +static const struct dfl_feature_ops port_afu_ops = { + .init = port_afu_init, + .uinit = port_afu_uinit, +}; + static struct dfl_feature_driver port_feature_drvs[] = { { .id = PORT_FEATURE_ID_HEADER, .ops = &port_hdr_ops, }, + { + .id = PORT_FEATURE_ID_AFU, + .ops = &port_afu_ops, + }, { .ops = NULL, } @@ -243,6 +306,64 @@ static long afu_ioctl_check_extension(struct dfl_feature_platform_data *pdata, return 0; } +static long +afu_ioctl_get_info(struct dfl_feature_platform_data *pdata, void __user *arg) +{ + struct dfl_fpga_port_info info; + struct dfl_afu *afu; + unsigned long minsz; + + minsz = offsetofend(struct dfl_fpga_port_info, num_umsgs); + + if (copy_from_user(&info, arg, minsz)) + return -EFAULT; + + if (info.argsz < minsz) + return -EINVAL; + + mutex_lock(&pdata->lock); + afu = dfl_fpga_pdata_get_private(pdata); + info.flags = 0; + info.num_regions = afu->num_regions; + info.num_umsgs = afu->num_umsgs; + mutex_unlock(&pdata->lock); + + if (copy_to_user(arg, &info, sizeof(info))) + return -EFAULT; + + return 0; +} + +static long afu_ioctl_get_region_info(struct dfl_feature_platform_data *pdata, + void __user *arg) +{ + struct dfl_fpga_port_region_info rinfo; + struct dfl_afu_mmio_region region; + unsigned long minsz; + long ret; + + minsz = offsetofend(struct dfl_fpga_port_region_info, offset); + + if (copy_from_user(&rinfo, arg, minsz)) + return -EFAULT; + + if (rinfo.argsz < minsz || rinfo.padding) + return -EINVAL; + + ret = afu_mmio_region_get_by_index(pdata, rinfo.index, ®ion); + if (ret) + return ret; + + rinfo.flags = region.flags; + rinfo.size = region.size; + rinfo.offset = region.offset; + + if (copy_to_user(arg, &rinfo, sizeof(rinfo))) + return -EFAULT; + + return 0; +} + static long afu_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { struct platform_device *pdev = filp->private_data; @@ -259,6 +380,10 @@ static long afu_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) return DFL_FPGA_API_VERSION; case DFL_FPGA_CHECK_EXTENSION: return afu_ioctl_check_extension(pdata, arg); + case DFL_FPGA_PORT_GET_INFO: + return afu_ioctl_get_info(pdata, (void __user *)arg); + case DFL_FPGA_PORT_GET_REGION_INFO: + return afu_ioctl_get_region_info(pdata, (void __user *)arg); default: /* * Let sub-feature's ioctl function to handle the cmd @@ -277,13 +402,83 @@ static long afu_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) return -EINVAL; } +static int afu_mmap(struct file *filp, struct vm_area_struct *vma) +{ + struct platform_device *pdev = filp->private_data; + struct dfl_feature_platform_data *pdata; + u64 size = vma->vm_end - vma->vm_start; + struct dfl_afu_mmio_region region; + u64 offset; + int ret; + + if (!(vma->vm_flags & VM_SHARED)) + return -EINVAL; + + pdata = dev_get_platdata(&pdev->dev); + + offset = vma->vm_pgoff << PAGE_SHIFT; + ret = afu_mmio_region_get_by_offset(pdata, offset, size, ®ion); + if (ret) + return ret; + + if (!(region.flags & DFL_PORT_REGION_MMAP)) + return -EINVAL; + + if ((vma->vm_flags & VM_READ) && !(region.flags & DFL_PORT_REGION_READ)) + return -EPERM; + + if ((vma->vm_flags & VM_WRITE) && + !(region.flags & DFL_PORT_REGION_WRITE)) + return -EPERM; + + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + return remap_pfn_range(vma, vma->vm_start, + (region.phys + (offset - region.offset)) >> PAGE_SHIFT, + size, vma->vm_page_prot); +} + static const struct file_operations afu_fops = { .owner = THIS_MODULE, .open = afu_open, .release = afu_release, .unlocked_ioctl = afu_ioctl, + .mmap = afu_mmap, }; +static int afu_dev_init(struct platform_device *pdev) +{ + struct dfl_feature_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct dfl_afu *afu; + + afu = devm_kzalloc(&pdev->dev, sizeof(*afu), GFP_KERNEL); + if (!afu) + return -ENOMEM; + + afu->pdata = pdata; + + mutex_lock(&pdata->lock); + dfl_fpga_pdata_set_private(pdata, afu); + afu_mmio_region_init(pdata); + mutex_unlock(&pdata->lock); + + return 0; +} + +static int afu_dev_destroy(struct platform_device *pdev) +{ + struct dfl_feature_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct dfl_afu *afu; + + mutex_lock(&pdata->lock); + afu = dfl_fpga_pdata_get_private(pdata); + afu_mmio_region_destroy(pdata); + dfl_fpga_pdata_set_private(pdata, NULL); + mutex_unlock(&pdata->lock); + + return 0; +} + static int port_enable_set(struct platform_device *pdev, bool enable) { struct dfl_feature_platform_data *pdata = dev_get_platdata(&pdev->dev); @@ -312,14 +507,25 @@ static int afu_probe(struct platform_device *pdev) dev_dbg(&pdev->dev, "%s\n", __func__); + ret = afu_dev_init(pdev); + if (ret) + goto exit; + ret = dfl_fpga_dev_feature_init(pdev, port_feature_drvs); if (ret) - return ret; + goto dev_destroy; ret = dfl_fpga_dev_ops_register(pdev, &afu_fops, THIS_MODULE); - if (ret) + if (ret) { dfl_fpga_dev_feature_uinit(pdev); + goto dev_destroy; + } + + return 0; +dev_destroy: + afu_dev_destroy(pdev); +exit: return ret; } @@ -329,6 +535,7 @@ static int afu_remove(struct platform_device *pdev) dfl_fpga_dev_ops_unregister(pdev); dfl_fpga_dev_feature_uinit(pdev); + afu_dev_destroy(pdev); return 0; } diff --git a/drivers/fpga/dfl-afu-region.c b/drivers/fpga/dfl-afu-region.c new file mode 100644 index 000000000000..0804b7a0c298 --- /dev/null +++ b/drivers/fpga/dfl-afu-region.c @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for FPGA Accelerated Function Unit (AFU) MMIO Region Management + * + * Copyright (C) 2017-2018 Intel Corporation, Inc. + * + * Authors: + * Wu Hao + * Xiao Guangrong + */ +#include "dfl-afu.h" + +/** + * afu_mmio_region_init - init function for afu mmio region support + * @pdata: afu platform device's pdata. + */ +void afu_mmio_region_init(struct dfl_feature_platform_data *pdata) +{ + struct dfl_afu *afu = dfl_fpga_pdata_get_private(pdata); + + INIT_LIST_HEAD(&afu->regions); +} + +#define for_each_region(region, afu) \ + list_for_each_entry((region), &(afu)->regions, node) + +static struct dfl_afu_mmio_region *get_region_by_index(struct dfl_afu *afu, + u32 region_index) +{ + struct dfl_afu_mmio_region *region; + + for_each_region(region, afu) + if (region->index == region_index) + return region; + + return NULL; +} + +/** + * afu_mmio_region_add - add a mmio region to given feature dev. + * + * @region_index: region index. + * @region_size: region size. + * @phys: region's physical address of this region. + * @flags: region flags (access permission). + * + * Return: 0 on success, negative error code otherwise. + */ +int afu_mmio_region_add(struct dfl_feature_platform_data *pdata, + u32 region_index, u64 region_size, u64 phys, u32 flags) +{ + struct dfl_afu_mmio_region *region; + struct dfl_afu *afu; + int ret = 0; + + region = devm_kzalloc(&pdata->dev->dev, sizeof(*region), GFP_KERNEL); + if (!region) + return -ENOMEM; + + region->index = region_index; + region->size = region_size; + region->phys = phys; + region->flags = flags; + + mutex_lock(&pdata->lock); + + afu = dfl_fpga_pdata_get_private(pdata); + + /* check if @index already exists */ + if (get_region_by_index(afu, region_index)) { + mutex_unlock(&pdata->lock); + ret = -EEXIST; + goto exit; + } + + region_size = PAGE_ALIGN(region_size); + region->offset = afu->region_cur_offset; + list_add(®ion->node, &afu->regions); + + afu->region_cur_offset += region_size; + afu->num_regions++; + mutex_unlock(&pdata->lock); + + return 0; + +exit: + devm_kfree(&pdata->dev->dev, region); + return ret; +} + +/** + * afu_mmio_region_destroy - destroy all mmio regions under given feature dev. + * @pdata: afu platform device's pdata. + */ +void afu_mmio_region_destroy(struct dfl_feature_platform_data *pdata) +{ + struct dfl_afu *afu = dfl_fpga_pdata_get_private(pdata); + struct dfl_afu_mmio_region *tmp, *region; + + list_for_each_entry_safe(region, tmp, &afu->regions, node) + devm_kfree(&pdata->dev->dev, region); +} + +/** + * afu_mmio_region_get_by_index - find an afu region by index. + * @pdata: afu platform device's pdata. + * @region_index: region index. + * @pregion: ptr to region for result. + * + * Return: 0 on success, negative error code otherwise. + */ +int afu_mmio_region_get_by_index(struct dfl_feature_platform_data *pdata, + u32 region_index, + struct dfl_afu_mmio_region *pregion) +{ + struct dfl_afu_mmio_region *region; + struct dfl_afu *afu; + int ret = 0; + + mutex_lock(&pdata->lock); + afu = dfl_fpga_pdata_get_private(pdata); + region = get_region_by_index(afu, region_index); + if (!region) { + ret = -EINVAL; + goto exit; + } + *pregion = *region; +exit: + mutex_unlock(&pdata->lock); + return ret; +} + +/** + * afu_mmio_region_get_by_offset - find an afu mmio region by offset and size + * + * @pdata: afu platform device's pdata. + * @offset: region offset from start of the device fd. + * @size: region size. + * @pregion: ptr to region for result. + * + * Find the region which fully contains the region described by input + * parameters (offset and size) from the feature dev's region linked list. + * + * Return: 0 on success, negative error code otherwise. + */ +int afu_mmio_region_get_by_offset(struct dfl_feature_platform_data *pdata, + u64 offset, u64 size, + struct dfl_afu_mmio_region *pregion) +{ + struct dfl_afu_mmio_region *region; + struct dfl_afu *afu; + int ret = 0; + + mutex_lock(&pdata->lock); + afu = dfl_fpga_pdata_get_private(pdata); + for_each_region(region, afu) + if (region->offset <= offset && + region->offset + region->size >= offset + size) { + *pregion = *region; + goto exit; + } + ret = -EINVAL; +exit: + mutex_unlock(&pdata->lock); + return ret; +} diff --git a/drivers/fpga/dfl-afu.h b/drivers/fpga/dfl-afu.h new file mode 100644 index 000000000000..11ce2cf99759 --- /dev/null +++ b/drivers/fpga/dfl-afu.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Header file for FPGA Accelerated Function Unit (AFU) Driver + * + * Copyright (C) 2017-2018 Intel Corporation, Inc. + * + * Authors: + * Wu Hao + * Xiao Guangrong + * Joseph Grecco + * Enno Luebbers + * Tim Whisonant + * Ananda Ravuri + * Henry Mitchel + */ + +#ifndef __DFL_AFU_H +#define __DFL_AFU_H + +#include + +#include "dfl.h" + +/** + * struct dfl_afu_mmio_region - afu mmio region data structure + * + * @index: region index. + * @flags: region flags (access permission). + * @size: region size. + * @offset: region offset from start of the device fd. + * @phys: region's physical address. + * @node: node to add to afu feature dev's region list. + */ +struct dfl_afu_mmio_region { + u32 index; + u32 flags; + u64 size; + u64 offset; + u64 phys; + struct list_head node; +}; + +/** + * struct dfl_afu - afu device data structure + * + * @region_cur_offset: current region offset from start to the device fd. + * @num_regions: num of mmio regions. + * @regions: the mmio region linked list of this afu feature device. + * @num_umsgs: num of umsgs. + * @pdata: afu platform device's pdata. + */ +struct dfl_afu { + u64 region_cur_offset; + int num_regions; + u8 num_umsgs; + struct list_head regions; + + struct dfl_feature_platform_data *pdata; +}; + +void afu_mmio_region_init(struct dfl_feature_platform_data *pdata); +int afu_mmio_region_add(struct dfl_feature_platform_data *pdata, + u32 region_index, u64 region_size, u64 phys, u32 flags); +void afu_mmio_region_destroy(struct dfl_feature_platform_data *pdata); +int afu_mmio_region_get_by_index(struct dfl_feature_platform_data *pdata, + u32 region_index, + struct dfl_afu_mmio_region *pregion); +int afu_mmio_region_get_by_offset(struct dfl_feature_platform_data *pdata, + u64 offset, u64 size, + struct dfl_afu_mmio_region *pregion); +#endif diff --git a/include/uapi/linux/fpga-dfl.h b/include/uapi/linux/fpga-dfl.h index e6b4dd26cc68..a3ccdfb115a5 100644 --- a/include/uapi/linux/fpga-dfl.h +++ b/include/uapi/linux/fpga-dfl.h @@ -66,6 +66,54 @@ #define DFL_FPGA_PORT_RESET _IO(DFL_FPGA_MAGIC, DFL_PORT_BASE + 0) +/** + * DFL_FPGA_PORT_GET_INFO - _IOR(DFL_FPGA_MAGIC, DFL_PORT_BASE + 1, + * struct dfl_fpga_port_info) + * + * Retrieve information about the fpga port. + * Driver fills the info in provided struct dfl_fpga_port_info. + * Return: 0 on success, -errno on failure. + */ +struct dfl_fpga_port_info { + /* Input */ + __u32 argsz; /* Structure length */ + /* Output */ + __u32 flags; /* Zero for now */ + __u32 num_regions; /* The number of supported regions */ + __u32 num_umsgs; /* The number of allocated umsgs */ +}; + +#define DFL_FPGA_PORT_GET_INFO _IO(DFL_FPGA_MAGIC, DFL_PORT_BASE + 1) + +/** + * FPGA_PORT_GET_REGION_INFO - _IOWR(FPGA_MAGIC, PORT_BASE + 2, + * struct dfl_fpga_port_region_info) + * + * Retrieve information about a device memory region. + * Caller provides struct dfl_fpga_port_region_info with index value set. + * Driver returns the region info in other fields. + * Return: 0 on success, -errno on failure. + */ +struct dfl_fpga_port_region_info { + /* input */ + __u32 argsz; /* Structure length */ + /* Output */ + __u32 flags; /* Access permission */ +#define DFL_PORT_REGION_READ (1 << 0) /* Region is readable */ +#define DFL_PORT_REGION_WRITE (1 << 1) /* Region is writable */ +#define DFL_PORT_REGION_MMAP (1 << 2) /* Can be mmaped to userspace */ + /* Input */ + __u32 index; /* Region index */ +#define DFL_PORT_REGION_INDEX_AFU 0 /* AFU */ +#define DFL_PORT_REGION_INDEX_STP 1 /* Signal Tap */ + __u32 padding; + /* Output */ + __u64 size; /* Region size (bytes) */ + __u64 offset; /* Region offset from start of device fd */ +}; + +#define DFL_FPGA_PORT_GET_REGION_INFO _IO(DFL_FPGA_MAGIC, DFL_PORT_BASE + 2) + /* IOCTLs for FME file descriptor */ /** -- cgit v1.2.3 From fa8dda1edef9ebc3af467c644c5533ac97171e12 Mon Sep 17 00:00:00 2001 From: Wu Hao Date: Sat, 30 Jun 2018 08:53:35 +0800 Subject: fpga: dfl: afu: add DFL_FPGA_PORT_DMA_MAP/UNMAP ioctls support DMA memory regions are required for Accelerated Function Unit (AFU) usage. These two ioctls allow user space applications to map user memory regions for dma, and unmap them after use. Iova is returned from driver to user space application via DFL_FPGA_PORT_DMA_MAP ioctl. Application needs to unmap it after use, otherwise, driver will unmap them in device file release operation. Each AFU has its own rb tree to keep track of its mapped DMA regions. Ioctl interfaces: * DFL_FPGA_PORT_DMA_MAP Do the dma mapping per user_addr and length provided by user. Return iova in provided struct dfl_fpga_port_dma_map. * DFL_FPGA_PORT_DMA_UNMAP Unmap the dma region per iova provided by user. Signed-off-by: Tim Whisonant Signed-off-by: Enno Luebbers Signed-off-by: Shiva Rao Signed-off-by: Christopher Rauer Signed-off-by: Xiao Guangrong Signed-off-by: Wu Hao Acked-by: Alan Tull Signed-off-by: Greg Kroah-Hartman --- drivers/fpga/Makefile | 2 +- drivers/fpga/dfl-afu-dma-region.c | 463 ++++++++++++++++++++++++++++++++++++++ drivers/fpga/dfl-afu-main.c | 61 ++++- drivers/fpga/dfl-afu.h | 31 ++- include/uapi/linux/fpga-dfl.h | 37 +++ 5 files changed, 591 insertions(+), 3 deletions(-) create mode 100644 drivers/fpga/dfl-afu-dma-region.c diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile index a44d50dd0b70..7a2d73ba7122 100644 --- a/drivers/fpga/Makefile +++ b/drivers/fpga/Makefile @@ -38,7 +38,7 @@ obj-$(CONFIG_FPGA_DFL_FME_REGION) += dfl-fme-region.o obj-$(CONFIG_FPGA_DFL_AFU) += dfl-afu.o dfl-fme-objs := dfl-fme-main.o dfl-fme-pr.o -dfl-afu-objs := dfl-afu-main.o dfl-afu-region.o +dfl-afu-objs := dfl-afu-main.o dfl-afu-region.o dfl-afu-dma-region.o # Drivers for FPGAs which implement DFL obj-$(CONFIG_FPGA_DFL_PCI) += dfl-pci.o diff --git a/drivers/fpga/dfl-afu-dma-region.c b/drivers/fpga/dfl-afu-dma-region.c new file mode 100644 index 000000000000..0e81d33af856 --- /dev/null +++ b/drivers/fpga/dfl-afu-dma-region.c @@ -0,0 +1,463 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for FPGA Accelerated Function Unit (AFU) DMA Region Management + * + * Copyright (C) 2017-2018 Intel Corporation, Inc. + * + * Authors: + * Wu Hao + * Xiao Guangrong + */ + +#include +#include +#include + +#include "dfl-afu.h" + +static void put_all_pages(struct page **pages, int npages) +{ + int i; + + for (i = 0; i < npages; i++) + if (pages[i]) + put_page(pages[i]); +} + +void afu_dma_region_init(struct dfl_feature_platform_data *pdata) +{ + struct dfl_afu *afu = dfl_fpga_pdata_get_private(pdata); + + afu->dma_regions = RB_ROOT; +} + +/** + * afu_dma_adjust_locked_vm - adjust locked memory + * @dev: port device + * @npages: number of pages + * @incr: increase or decrease locked memory + * + * Increase or decrease the locked memory size with npages input. + * + * Return 0 on success. + * Return -ENOMEM if locked memory size is over the limit and no CAP_IPC_LOCK. + */ +static int afu_dma_adjust_locked_vm(struct device *dev, long npages, bool incr) +{ + unsigned long locked, lock_limit; + int ret = 0; + + /* the task is exiting. */ + if (!current->mm) + return 0; + + down_write(¤t->mm->mmap_sem); + + if (incr) { + locked = current->mm->locked_vm + npages; + lock_limit = rlimit(RLIMIT_MEMLOCK) >> PAGE_SHIFT; + + if (locked > lock_limit && !capable(CAP_IPC_LOCK)) + ret = -ENOMEM; + else + current->mm->locked_vm += npages; + } else { + if (WARN_ON_ONCE(npages > current->mm->locked_vm)) + npages = current->mm->locked_vm; + current->mm->locked_vm -= npages; + } + + dev_dbg(dev, "[%d] RLIMIT_MEMLOCK %c%ld %ld/%ld%s\n", current->pid, + incr ? '+' : '-', npages << PAGE_SHIFT, + current->mm->locked_vm << PAGE_SHIFT, rlimit(RLIMIT_MEMLOCK), + ret ? "- execeeded" : ""); + + up_write(¤t->mm->mmap_sem); + + return ret; +} + +/** + * afu_dma_pin_pages - pin pages of given dma memory region + * @pdata: feature device platform data + * @region: dma memory region to be pinned + * + * Pin all the pages of given dfl_afu_dma_region. + * Return 0 for success or negative error code. + */ +static int afu_dma_pin_pages(struct dfl_feature_platform_data *pdata, + struct dfl_afu_dma_region *region) +{ + int npages = region->length >> PAGE_SHIFT; + struct device *dev = &pdata->dev->dev; + int ret, pinned; + + ret = afu_dma_adjust_locked_vm(dev, npages, true); + if (ret) + return ret; + + region->pages = kcalloc(npages, sizeof(struct page *), GFP_KERNEL); + if (!region->pages) { + ret = -ENOMEM; + goto unlock_vm; + } + + pinned = get_user_pages_fast(region->user_addr, npages, 1, + region->pages); + if (pinned < 0) { + ret = pinned; + goto put_pages; + } else if (pinned != npages) { + ret = -EFAULT; + goto free_pages; + } + + dev_dbg(dev, "%d pages pinned\n", pinned); + + return 0; + +put_pages: + put_all_pages(region->pages, pinned); +free_pages: + kfree(region->pages); +unlock_vm: + afu_dma_adjust_locked_vm(dev, npages, false); + return ret; +} + +/** + * afu_dma_unpin_pages - unpin pages of given dma memory region + * @pdata: feature device platform data + * @region: dma memory region to be unpinned + * + * Unpin all the pages of given dfl_afu_dma_region. + * Return 0 for success or negative error code. + */ +static void afu_dma_unpin_pages(struct dfl_feature_platform_data *pdata, + struct dfl_afu_dma_region *region) +{ + long npages = region->length >> PAGE_SHIFT; + struct device *dev = &pdata->dev->dev; + + put_all_pages(region->pages, npages); + kfree(region->pages); + afu_dma_adjust_locked_vm(dev, npages, false); + + dev_dbg(dev, "%ld pages unpinned\n", npages); +} + +/** + * afu_dma_check_continuous_pages - check if pages are continuous + * @region: dma memory region + * + * Return true if pages of given dma memory region have continuous physical + * address, otherwise return false. + */ +static bool afu_dma_check_continuous_pages(struct dfl_afu_dma_region *region) +{ + int npages = region->length >> PAGE_SHIFT; + int i; + + for (i = 0; i < npages - 1; i++) + if (page_to_pfn(region->pages[i]) + 1 != + page_to_pfn(region->pages[i + 1])) + return false; + + return true; +} + +/** + * dma_region_check_iova - check if memory area is fully contained in the region + * @region: dma memory region + * @iova: address of the dma memory area + * @size: size of the dma memory area + * + * Compare the dma memory area defined by @iova and @size with given dma region. + * Return true if memory area is fully contained in the region, otherwise false. + */ +static bool dma_region_check_iova(struct dfl_afu_dma_region *region, + u64 iova, u64 size) +{ + if (!size && region->iova != iova) + return false; + + return (region->iova <= iova) && + (region->length + region->iova >= iova + size); +} + +/** + * afu_dma_region_add - add given dma region to rbtree + * @pdata: feature device platform data + * @region: dma region to be added + * + * Return 0 for success, -EEXIST if dma region has already been added. + * + * Needs to be called with pdata->lock heold. + */ +static int afu_dma_region_add(struct dfl_feature_platform_data *pdata, + struct dfl_afu_dma_region *region) +{ + struct dfl_afu *afu = dfl_fpga_pdata_get_private(pdata); + struct rb_node **new, *parent = NULL; + + dev_dbg(&pdata->dev->dev, "add region (iova = %llx)\n", + (unsigned long long)region->iova); + + new = &afu->dma_regions.rb_node; + + while (*new) { + struct dfl_afu_dma_region *this; + + this = container_of(*new, struct dfl_afu_dma_region, node); + + parent = *new; + + if (dma_region_check_iova(this, region->iova, region->length)) + return -EEXIST; + + if (region->iova < this->iova) + new = &((*new)->rb_left); + else if (region->iova > this->iova) + new = &((*new)->rb_right); + else + return -EEXIST; + } + + rb_link_node(®ion->node, parent, new); + rb_insert_color(®ion->node, &afu->dma_regions); + + return 0; +} + +/** + * afu_dma_region_remove - remove given dma region from rbtree + * @pdata: feature device platform data + * @region: dma region to be removed + * + * Needs to be called with pdata->lock heold. + */ +static void afu_dma_region_remove(struct dfl_feature_platform_data *pdata, + struct dfl_afu_dma_region *region) +{ + struct dfl_afu *afu; + + dev_dbg(&pdata->dev->dev, "del region (iova = %llx)\n", + (unsigned long long)region->iova); + + afu = dfl_fpga_pdata_get_private(pdata); + rb_erase(®ion->node, &afu->dma_regions); +} + +/** + * afu_dma_region_destroy - destroy all regions in rbtree + * @pdata: feature device platform data + * + * Needs to be called with pdata->lock heold. + */ +void afu_dma_region_destroy(struct dfl_feature_platform_data *pdata) +{ + struct dfl_afu *afu = dfl_fpga_pdata_get_private(pdata); + struct rb_node *node = rb_first(&afu->dma_regions); + struct dfl_afu_dma_region *region; + + while (node) { + region = container_of(node, struct dfl_afu_dma_region, node); + + dev_dbg(&pdata->dev->dev, "del region (iova = %llx)\n", + (unsigned long long)region->iova); + + rb_erase(node, &afu->dma_regions); + + if (region->iova) + dma_unmap_page(dfl_fpga_pdata_to_parent(pdata), + region->iova, region->length, + DMA_BIDIRECTIONAL); + + if (region->pages) + afu_dma_unpin_pages(pdata, region); + + node = rb_next(node); + kfree(region); + } +} + +/** + * afu_dma_region_find - find the dma region from rbtree based on iova and size + * @pdata: feature device platform data + * @iova: address of the dma memory area + * @size: size of the dma memory area + * + * It finds the dma region from the rbtree based on @iova and @size: + * - if @size == 0, it finds the dma region which starts from @iova + * - otherwise, it finds the dma region which fully contains + * [@iova, @iova+size) + * If nothing is matched returns NULL. + * + * Needs to be called with pdata->lock held. + */ +struct dfl_afu_dma_region * +afu_dma_region_find(struct dfl_feature_platform_data *pdata, u64 iova, u64 size) +{ + struct dfl_afu *afu = dfl_fpga_pdata_get_private(pdata); + struct rb_node *node = afu->dma_regions.rb_node; + struct device *dev = &pdata->dev->dev; + + while (node) { + struct dfl_afu_dma_region *region; + + region = container_of(node, struct dfl_afu_dma_region, node); + + if (dma_region_check_iova(region, iova, size)) { + dev_dbg(dev, "find region (iova = %llx)\n", + (unsigned long long)region->iova); + return region; + } + + if (iova < region->iova) + node = node->rb_left; + else if (iova > region->iova) + node = node->rb_right; + else + /* the iova region is not fully covered. */ + break; + } + + dev_dbg(dev, "region with iova %llx and size %llx is not found\n", + (unsigned long long)iova, (unsigned long long)size); + + return NULL; +} + +/** + * afu_dma_region_find_iova - find the dma region from rbtree by iova + * @pdata: feature device platform data + * @iova: address of the dma region + * + * Needs to be called with pdata->lock held. + */ +static struct dfl_afu_dma_region * +afu_dma_region_find_iova(struct dfl_feature_platform_data *pdata, u64 iova) +{ + return afu_dma_region_find(pdata, iova, 0); +} + +/** + * afu_dma_map_region - map memory region for dma + * @pdata: feature device platform data + * @user_addr: address of the memory region + * @length: size of the memory region + * @iova: pointer of iova address + * + * Map memory region defined by @user_addr and @length, and return dma address + * of the memory region via @iova. + * Return 0 for success, otherwise error code. + */ +int afu_dma_map_region(struct dfl_feature_platform_data *pdata, + u64 user_addr, u64 length, u64 *iova) +{ + struct dfl_afu_dma_region *region; + int ret; + + /* + * Check Inputs, only accept page-aligned user memory region with + * valid length. + */ + if (!PAGE_ALIGNED(user_addr) || !PAGE_ALIGNED(length) || !length) + return -EINVAL; + + /* Check overflow */ + if (user_addr + length < user_addr) + return -EINVAL; + + if (!access_ok(VERIFY_WRITE, (void __user *)(unsigned long)user_addr, + length)) + return -EINVAL; + + region = kzalloc(sizeof(*region), GFP_KERNEL); + if (!region) + return -ENOMEM; + + region->user_addr = user_addr; + region->length = length; + + /* Pin the user memory region */ + ret = afu_dma_pin_pages(pdata, region); + if (ret) { + dev_err(&pdata->dev->dev, "failed to pin memory region\n"); + goto free_region; + } + + /* Only accept continuous pages, return error else */ + if (!afu_dma_check_continuous_pages(region)) { + dev_err(&pdata->dev->dev, "pages are not continuous\n"); + ret = -EINVAL; + goto unpin_pages; + } + + /* As pages are continuous then start to do DMA mapping */ + region->iova = dma_map_page(dfl_fpga_pdata_to_parent(pdata), + region->pages[0], 0, + region->length, + DMA_BIDIRECTIONAL); + if (dma_mapping_error(&pdata->dev->dev, region->iova)) { + dev_err(&pdata->dev->dev, "failed to map for dma\n"); + ret = -EFAULT; + goto unpin_pages; + } + + *iova = region->iova; + + mutex_lock(&pdata->lock); + ret = afu_dma_region_add(pdata, region); + mutex_unlock(&pdata->lock); + if (ret) { + dev_err(&pdata->dev->dev, "failed to add dma region\n"); + goto unmap_dma; + } + + return 0; + +unmap_dma: + dma_unmap_page(dfl_fpga_pdata_to_parent(pdata), + region->iova, region->length, DMA_BIDIRECTIONAL); +unpin_pages: + afu_dma_unpin_pages(pdata, region); +free_region: + kfree(region); + return ret; +} + +/** + * afu_dma_unmap_region - unmap dma memory region + * @pdata: feature device platform data + * @iova: dma address of the region + * + * Unmap dma memory region based on @iova. + * Return 0 for success, otherwise error code. + */ +int afu_dma_unmap_region(struct dfl_feature_platform_data *pdata, u64 iova) +{ + struct dfl_afu_dma_region *region; + + mutex_lock(&pdata->lock); + region = afu_dma_region_find_iova(pdata, iova); + if (!region) { + mutex_unlock(&pdata->lock); + return -EINVAL; + } + + if (region->in_use) { + mutex_unlock(&pdata->lock); + return -EBUSY; + } + + afu_dma_region_remove(pdata, region); + mutex_unlock(&pdata->lock); + + dma_unmap_page(dfl_fpga_pdata_to_parent(pdata), + region->iova, region->length, DMA_BIDIRECTIONAL); + afu_dma_unpin_pages(pdata, region); + kfree(region); + + return 0; +} diff --git a/drivers/fpga/dfl-afu-main.c b/drivers/fpga/dfl-afu-main.c index f67a78d7e9ad..02baa6a227c0 100644 --- a/drivers/fpga/dfl-afu-main.c +++ b/drivers/fpga/dfl-afu-main.c @@ -293,7 +293,11 @@ static int afu_release(struct inode *inode, struct file *filp) pdata = dev_get_platdata(&pdev->dev); - port_reset(pdev); + mutex_lock(&pdata->lock); + __port_reset(pdev); + afu_dma_region_destroy(pdata); + mutex_unlock(&pdata->lock); + dfl_feature_dev_use_end(pdata); return 0; @@ -364,6 +368,55 @@ static long afu_ioctl_get_region_info(struct dfl_feature_platform_data *pdata, return 0; } +static long +afu_ioctl_dma_map(struct dfl_feature_platform_data *pdata, void __user *arg) +{ + struct dfl_fpga_port_dma_map map; + unsigned long minsz; + long ret; + + minsz = offsetofend(struct dfl_fpga_port_dma_map, iova); + + if (copy_from_user(&map, arg, minsz)) + return -EFAULT; + + if (map.argsz < minsz || map.flags) + return -EINVAL; + + ret = afu_dma_map_region(pdata, map.user_addr, map.length, &map.iova); + if (ret) + return ret; + + if (copy_to_user(arg, &map, sizeof(map))) { + afu_dma_unmap_region(pdata, map.iova); + return -EFAULT; + } + + dev_dbg(&pdata->dev->dev, "dma map: ua=%llx, len=%llx, iova=%llx\n", + (unsigned long long)map.user_addr, + (unsigned long long)map.length, + (unsigned long long)map.iova); + + return 0; +} + +static long +afu_ioctl_dma_unmap(struct dfl_feature_platform_data *pdata, void __user *arg) +{ + struct dfl_fpga_port_dma_unmap unmap; + unsigned long minsz; + + minsz = offsetofend(struct dfl_fpga_port_dma_unmap, iova); + + if (copy_from_user(&unmap, arg, minsz)) + return -EFAULT; + + if (unmap.argsz < minsz || unmap.flags) + return -EINVAL; + + return afu_dma_unmap_region(pdata, unmap.iova); +} + static long afu_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { struct platform_device *pdev = filp->private_data; @@ -384,6 +437,10 @@ static long afu_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) return afu_ioctl_get_info(pdata, (void __user *)arg); case DFL_FPGA_PORT_GET_REGION_INFO: return afu_ioctl_get_region_info(pdata, (void __user *)arg); + case DFL_FPGA_PORT_DMA_MAP: + return afu_ioctl_dma_map(pdata, (void __user *)arg); + case DFL_FPGA_PORT_DMA_UNMAP: + return afu_ioctl_dma_unmap(pdata, (void __user *)arg); default: /* * Let sub-feature's ioctl function to handle the cmd @@ -460,6 +517,7 @@ static int afu_dev_init(struct platform_device *pdev) mutex_lock(&pdata->lock); dfl_fpga_pdata_set_private(pdata, afu); afu_mmio_region_init(pdata); + afu_dma_region_init(pdata); mutex_unlock(&pdata->lock); return 0; @@ -473,6 +531,7 @@ static int afu_dev_destroy(struct platform_device *pdev) mutex_lock(&pdata->lock); afu = dfl_fpga_pdata_get_private(pdata); afu_mmio_region_destroy(pdata); + afu_dma_region_destroy(pdata); dfl_fpga_pdata_set_private(pdata, NULL); mutex_unlock(&pdata->lock); diff --git a/drivers/fpga/dfl-afu.h b/drivers/fpga/dfl-afu.h index 11ce2cf99759..0c7630ae3cda 100644 --- a/drivers/fpga/dfl-afu.h +++ b/drivers/fpga/dfl-afu.h @@ -40,12 +40,32 @@ struct dfl_afu_mmio_region { struct list_head node; }; +/** + * struct fpga_afu_dma_region - afu DMA region data structure + * + * @user_addr: region userspace virtual address. + * @length: region length. + * @iova: region IO virtual address. + * @pages: ptr to pages of this region. + * @node: rb tree node. + * @in_use: flag to indicate if this region is in_use. + */ +struct dfl_afu_dma_region { + u64 user_addr; + u64 length; + u64 iova; + struct page **pages; + struct rb_node node; + bool in_use; +}; + /** * struct dfl_afu - afu device data structure * * @region_cur_offset: current region offset from start to the device fd. * @num_regions: num of mmio regions. * @regions: the mmio region linked list of this afu feature device. + * @dma_regions: root of dma regions rb tree. * @num_umsgs: num of umsgs. * @pdata: afu platform device's pdata. */ @@ -54,6 +74,7 @@ struct dfl_afu { int num_regions; u8 num_umsgs; struct list_head regions; + struct rb_root dma_regions; struct dfl_feature_platform_data *pdata; }; @@ -68,4 +89,12 @@ int afu_mmio_region_get_by_index(struct dfl_feature_platform_data *pdata, int afu_mmio_region_get_by_offset(struct dfl_feature_platform_data *pdata, u64 offset, u64 size, struct dfl_afu_mmio_region *pregion); -#endif +void afu_dma_region_init(struct dfl_feature_platform_data *pdata); +void afu_dma_region_destroy(struct dfl_feature_platform_data *pdata); +int afu_dma_map_region(struct dfl_feature_platform_data *pdata, + u64 user_addr, u64 length, u64 *iova); +int afu_dma_unmap_region(struct dfl_feature_platform_data *pdata, u64 iova); +struct dfl_afu_dma_region * +afu_dma_region_find(struct dfl_feature_platform_data *pdata, + u64 iova, u64 size); +#endif /* __DFL_AFU_H */ diff --git a/include/uapi/linux/fpga-dfl.h b/include/uapi/linux/fpga-dfl.h index a3ccdfb115a5..2e324e515c41 100644 --- a/include/uapi/linux/fpga-dfl.h +++ b/include/uapi/linux/fpga-dfl.h @@ -114,6 +114,43 @@ struct dfl_fpga_port_region_info { #define DFL_FPGA_PORT_GET_REGION_INFO _IO(DFL_FPGA_MAGIC, DFL_PORT_BASE + 2) +/** + * DFL_FPGA_PORT_DMA_MAP - _IOWR(DFL_FPGA_MAGIC, DFL_PORT_BASE + 3, + * struct dfl_fpga_port_dma_map) + * + * Map the dma memory per user_addr and length which are provided by caller. + * Driver fills the iova in provided struct afu_port_dma_map. + * This interface only accepts page-size aligned user memory for dma mapping. + * Return: 0 on success, -errno on failure. + */ +struct dfl_fpga_port_dma_map { + /* Input */ + __u32 argsz; /* Structure length */ + __u32 flags; /* Zero for now */ + __u64 user_addr; /* Process virtual address */ + __u64 length; /* Length of mapping (bytes)*/ + /* Output */ + __u64 iova; /* IO virtual address */ +}; + +#define DFL_FPGA_PORT_DMA_MAP _IO(DFL_FPGA_MAGIC, DFL_PORT_BASE + 3) + +/** + * DFL_FPGA_PORT_DMA_UNMAP - _IOW(FPGA_MAGIC, PORT_BASE + 4, + * struct dfl_fpga_port_dma_unmap) + * + * Unmap the dma memory per iova provided by caller. + * Return: 0 on success, -errno on failure. + */ +struct dfl_fpga_port_dma_unmap { + /* Input */ + __u32 argsz; /* Structure length */ + __u32 flags; /* Zero for now */ + __u64 iova; /* IO virtual address */ +}; + +#define DFL_FPGA_PORT_DMA_UNMAP _IO(DFL_FPGA_MAGIC, DFL_PORT_BASE + 4) + /* IOCTLs for FME file descriptor */ /** -- cgit v1.2.3 From 5d6bd30cc9c3fca974d554bcd7bbf5f77fbe3238 Mon Sep 17 00:00:00 2001 From: Wu Hao Date: Sat, 30 Jun 2018 08:53:36 +0800 Subject: MAINTAINERS: add entry for FPGA DFL drivers Add entry for FPGA Device Feature List (DFL) drivers. Signed-off-by: Wu Hao Acked-by: Alan Tull Acked-by: Moritz Fischer Signed-off-by: Greg Kroah-Hartman --- MAINTAINERS | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index b51c8cb6faed..e071620833b1 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5634,6 +5634,14 @@ F: drivers/fpga/ F: include/linux/fpga/ W: http://www.rocketboards.org +FPGA DFL DRIVERS +M: Wu Hao +L: linux-fpga@vger.kernel.org +S: Maintained +F: Documentation/fpga/dfl.txt +F: include/uapi/linux/fpga-dfl.h +F: drivers/fpga/dfl* + FPU EMULATOR M: Bill Metzenthen W: http://floatingpoint.sourceforge.net/emulator/index.html -- cgit v1.2.3 From 3eb93e0825abf76ddb7960506be71e6f1a63ad17 Mon Sep 17 00:00:00 2001 From: Baolin Wang Date: Wed, 11 Jul 2018 11:20:40 +0100 Subject: dt-bindings: nvmem: Add Spreadtrum SC27XX efuse controller documentation This patch adds the binding documentation for Spreadtrum SC27XX series PMICs efuse controller device. Signed-off-by: Baolin Wang Reviewed-by: Rob Herring Signed-off-by: Srinivas Kandagatla Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/nvmem/sc27xx-efuse.txt | 52 ++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 Documentation/devicetree/bindings/nvmem/sc27xx-efuse.txt diff --git a/Documentation/devicetree/bindings/nvmem/sc27xx-efuse.txt b/Documentation/devicetree/bindings/nvmem/sc27xx-efuse.txt new file mode 100644 index 000000000000..586c08286aa9 --- /dev/null +++ b/Documentation/devicetree/bindings/nvmem/sc27xx-efuse.txt @@ -0,0 +1,52 @@ += Spreadtrum SC27XX PMIC eFuse device tree bindings = + +Required properties: +- compatible: Should be one of the following. + "sprd,sc2720-efuse" + "sprd,sc2721-efuse" + "sprd,sc2723-efuse" + "sprd,sc2730-efuse" + "sprd,sc2731-efuse" +- reg: Specify the address offset of efuse controller. +- hwlocks: Reference to a phandle of a hwlock provider node. + += Data cells = +Are child nodes of eFuse, bindings of which as described in +bindings/nvmem/nvmem.txt + +Example: + + sc2731_pmic: pmic@0 { + compatible = "sprd,sc2731"; + reg = <0>; + spi-max-frequency = <26000000>; + interrupts = ; + interrupt-controller; + #interrupt-cells = <2>; + #address-cells = <1>; + #size-cells = <0>; + + efuse@380 { + compatible = "sprd,sc2731-efuse"; + reg = <0x380>; + #address-cells = <1>; + #size-cells = <1>; + hwlocks = <&hwlock 12>; + + /* Data cells */ + thermal_calib: calib@10 { + reg = <0x10 0x2>; + }; + }; + }; + += Data consumers = +Are device nodes which consume nvmem data cells. + +Example: + + thermal { + ... + nvmem-cells = <&thermal_calib>; + nvmem-cell-names = "calibration"; + }; -- cgit v1.2.3 From 19c54468f222d61f07ec83d13e46a4c78d326c80 Mon Sep 17 00:00:00 2001 From: Freeman Liu Date: Wed, 11 Jul 2018 11:20:41 +0100 Subject: nvmem: Add Spreadtrum SC27XX efuse support This patch add the efuse driver which is embeded in Spreadtrum SC27XX series PMICs. The sc27xx efuse contains 32 blocks and each block's data width is 16 bits. Signed-off-by: Freeman Liu Signed-off-by: Baolin Wang Signed-off-by: Srinivas Kandagatla Signed-off-by: Greg Kroah-Hartman --- drivers/nvmem/Kconfig | 11 ++ drivers/nvmem/Makefile | 3 +- drivers/nvmem/sc27xx-efuse.c | 264 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 277 insertions(+), 1 deletion(-) create mode 100644 drivers/nvmem/sc27xx-efuse.c diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig index 54a3c298247b..0a7a470ee859 100644 --- a/drivers/nvmem/Kconfig +++ b/drivers/nvmem/Kconfig @@ -181,4 +181,15 @@ config RAVE_SP_EEPROM help Say y here to enable Rave SP EEPROM support. +config SC27XX_EFUSE + tristate "Spreadtrum SC27XX eFuse Support" + depends on MFD_SC27XX_PMIC || COMPILE_TEST + depends on HAS_IOMEM + help + This is a simple driver to dump specified values of Spreadtrum + SC27XX PMICs from eFuse. + + This driver can also be built as a module. If so, the module + will be called nvmem-sc27xx-efuse. + endif diff --git a/drivers/nvmem/Makefile b/drivers/nvmem/Makefile index 27e96a8efd1c..4e8c61628f1a 100644 --- a/drivers/nvmem/Makefile +++ b/drivers/nvmem/Makefile @@ -39,4 +39,5 @@ obj-$(CONFIG_NVMEM_SNVS_LPGPR) += nvmem_snvs_lpgpr.o nvmem_snvs_lpgpr-y := snvs_lpgpr.o obj-$(CONFIG_RAVE_SP_EEPROM) += nvmem-rave-sp-eeprom.o nvmem-rave-sp-eeprom-y := rave-sp-eeprom.o - +obj-$(CONFIG_SC27XX_EFUSE) += nvmem-sc27xx-efuse.o +nvmem-sc27xx-efuse-y := sc27xx-efuse.o diff --git a/drivers/nvmem/sc27xx-efuse.c b/drivers/nvmem/sc27xx-efuse.c new file mode 100644 index 000000000000..33185d8d82cf --- /dev/null +++ b/drivers/nvmem/sc27xx-efuse.c @@ -0,0 +1,264 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2018 Spreadtrum Communications Inc. + +#include +#include +#include +#include +#include +#include + +/* PMIC global registers definition */ +#define SC27XX_MODULE_EN 0xc08 +#define SC27XX_EFUSE_EN BIT(6) + +/* Efuse controller registers definition */ +#define SC27XX_EFUSE_GLB_CTRL 0x0 +#define SC27XX_EFUSE_DATA_RD 0x4 +#define SC27XX_EFUSE_DATA_WR 0x8 +#define SC27XX_EFUSE_BLOCK_INDEX 0xc +#define SC27XX_EFUSE_MODE_CTRL 0x10 +#define SC27XX_EFUSE_STATUS 0x14 +#define SC27XX_EFUSE_WR_TIMING_CTRL 0x20 +#define SC27XX_EFUSE_RD_TIMING_CTRL 0x24 +#define SC27XX_EFUSE_EFUSE_DEB_CTRL 0x28 + +/* Mask definition for SC27XX_EFUSE_BLOCK_INDEX register */ +#define SC27XX_EFUSE_BLOCK_MASK GENMASK(4, 0) + +/* Bits definitions for SC27XX_EFUSE_MODE_CTRL register */ +#define SC27XX_EFUSE_PG_START BIT(0) +#define SC27XX_EFUSE_RD_START BIT(1) +#define SC27XX_EFUSE_CLR_RDDONE BIT(2) + +/* Bits definitions for SC27XX_EFUSE_STATUS register */ +#define SC27XX_EFUSE_PGM_BUSY BIT(0) +#define SC27XX_EFUSE_READ_BUSY BIT(1) +#define SC27XX_EFUSE_STANDBY BIT(2) +#define SC27XX_EFUSE_GLOBAL_PROT BIT(3) +#define SC27XX_EFUSE_RD_DONE BIT(4) + +/* Block number and block width (bytes) definitions */ +#define SC27XX_EFUSE_BLOCK_MAX 32 +#define SC27XX_EFUSE_BLOCK_WIDTH 2 + +/* Timeout (ms) for the trylock of hardware spinlocks */ +#define SC27XX_EFUSE_HWLOCK_TIMEOUT 5000 + +/* Timeout (us) of polling the status */ +#define SC27XX_EFUSE_POLL_TIMEOUT 3000000 +#define SC27XX_EFUSE_POLL_DELAY_US 10000 + +struct sc27xx_efuse { + struct device *dev; + struct regmap *regmap; + struct hwspinlock *hwlock; + struct mutex mutex; + u32 base; +}; + +/* + * On Spreadtrum platform, we have multi-subsystems will access the unique + * efuse controller, so we need one hardware spinlock to synchronize between + * the multiple subsystems. + */ +static int sc27xx_efuse_lock(struct sc27xx_efuse *efuse) +{ + int ret; + + mutex_lock(&efuse->mutex); + + ret = hwspin_lock_timeout_raw(efuse->hwlock, + SC27XX_EFUSE_HWLOCK_TIMEOUT); + if (ret) { + dev_err(efuse->dev, "timeout to get the hwspinlock\n"); + mutex_unlock(&efuse->mutex); + return ret; + } + + return 0; +} + +static void sc27xx_efuse_unlock(struct sc27xx_efuse *efuse) +{ + hwspin_unlock_raw(efuse->hwlock); + mutex_unlock(&efuse->mutex); +} + +static int sc27xx_efuse_poll_status(struct sc27xx_efuse *efuse, u32 bits) +{ + int ret; + u32 val; + + ret = regmap_read_poll_timeout(efuse->regmap, + efuse->base + SC27XX_EFUSE_STATUS, + val, (val & bits), + SC27XX_EFUSE_POLL_DELAY_US, + SC27XX_EFUSE_POLL_TIMEOUT); + if (ret) { + dev_err(efuse->dev, "timeout to update the efuse status\n"); + return ret; + } + + return 0; +} + +static int sc27xx_efuse_read(void *context, u32 offset, void *val, size_t bytes) +{ + struct sc27xx_efuse *efuse = context; + u32 buf; + int ret; + + if (offset > SC27XX_EFUSE_BLOCK_MAX || bytes > SC27XX_EFUSE_BLOCK_WIDTH) + return -EINVAL; + + ret = sc27xx_efuse_lock(efuse); + if (ret) + return ret; + + /* Enable the efuse controller. */ + ret = regmap_update_bits(efuse->regmap, SC27XX_MODULE_EN, + SC27XX_EFUSE_EN, SC27XX_EFUSE_EN); + if (ret) + goto unlock_efuse; + + /* + * Before reading, we should ensure the efuse controller is in + * standby state. + */ + ret = sc27xx_efuse_poll_status(efuse, SC27XX_EFUSE_STANDBY); + if (ret) + goto disable_efuse; + + /* Set the block address to be read. */ + ret = regmap_write(efuse->regmap, + efuse->base + SC27XX_EFUSE_BLOCK_INDEX, + offset & SC27XX_EFUSE_BLOCK_MASK); + if (ret) + goto disable_efuse; + + /* Start reading process from efuse memory. */ + ret = regmap_update_bits(efuse->regmap, + efuse->base + SC27XX_EFUSE_MODE_CTRL, + SC27XX_EFUSE_RD_START, + SC27XX_EFUSE_RD_START); + if (ret) + goto disable_efuse; + + /* + * Polling the read done status to make sure the reading process + * is completed, that means the data can be read out now. + */ + ret = sc27xx_efuse_poll_status(efuse, SC27XX_EFUSE_RD_DONE); + if (ret) + goto disable_efuse; + + /* Read data from efuse memory. */ + ret = regmap_read(efuse->regmap, efuse->base + SC27XX_EFUSE_DATA_RD, + &buf); + if (ret) + goto disable_efuse; + + /* Clear the read done flag. */ + ret = regmap_update_bits(efuse->regmap, + efuse->base + SC27XX_EFUSE_MODE_CTRL, + SC27XX_EFUSE_CLR_RDDONE, + SC27XX_EFUSE_CLR_RDDONE); + +disable_efuse: + /* Disable the efuse controller after reading. */ + regmap_update_bits(efuse->regmap, SC27XX_MODULE_EN, SC27XX_EFUSE_EN, 0); +unlock_efuse: + sc27xx_efuse_unlock(efuse); + + if (!ret) + memcpy(val, &buf, bytes); + + return ret; +} + +static int sc27xx_efuse_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct nvmem_config econfig = { }; + struct nvmem_device *nvmem; + struct sc27xx_efuse *efuse; + int ret; + + efuse = devm_kzalloc(&pdev->dev, sizeof(*efuse), GFP_KERNEL); + if (!efuse) + return -ENOMEM; + + efuse->regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!efuse->regmap) { + dev_err(&pdev->dev, "failed to get efuse regmap\n"); + return -ENODEV; + } + + ret = of_property_read_u32(np, "reg", &efuse->base); + if (ret) { + dev_err(&pdev->dev, "failed to get efuse base address\n"); + return ret; + } + + ret = of_hwspin_lock_get_id(np, 0); + if (ret < 0) { + dev_err(&pdev->dev, "failed to get hwspinlock id\n"); + return ret; + } + + efuse->hwlock = hwspin_lock_request_specific(ret); + if (!efuse->hwlock) { + dev_err(&pdev->dev, "failed to request hwspinlock\n"); + return -ENXIO; + } + + mutex_init(&efuse->mutex); + efuse->dev = &pdev->dev; + platform_set_drvdata(pdev, efuse); + + econfig.stride = 1; + econfig.word_size = 1; + econfig.read_only = true; + econfig.name = "sc27xx-efuse"; + econfig.size = SC27XX_EFUSE_BLOCK_MAX * SC27XX_EFUSE_BLOCK_WIDTH; + econfig.reg_read = sc27xx_efuse_read; + econfig.priv = efuse; + econfig.dev = &pdev->dev; + nvmem = devm_nvmem_register(&pdev->dev, &econfig); + if (IS_ERR(nvmem)) { + dev_err(&pdev->dev, "failed to register nvmem config\n"); + hwspin_lock_free(efuse->hwlock); + return PTR_ERR(nvmem); + } + + return 0; +} + +static int sc27xx_efuse_remove(struct platform_device *pdev) +{ + struct sc27xx_efuse *efuse = platform_get_drvdata(pdev); + + hwspin_lock_free(efuse->hwlock); + return 0; +} + +static const struct of_device_id sc27xx_efuse_of_match[] = { + { .compatible = "sprd,sc2731-efuse" }, + { } +}; + +static struct platform_driver sc27xx_efuse_driver = { + .probe = sc27xx_efuse_probe, + .remove = sc27xx_efuse_remove, + .driver = { + .name = "sc27xx-efuse", + .of_match_table = sc27xx_efuse_of_match, + }, +}; + +module_platform_driver(sc27xx_efuse_driver); + +MODULE_AUTHOR("Freeman Liu "); +MODULE_DESCRIPTION("Spreadtrum SC27xx efuse driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From c6e9a33468249a6d47a31d732a54fa3a42129f90 Mon Sep 17 00:00:00 2001 From: Anson Huang Date: Wed, 11 Jul 2018 11:20:42 +0100 Subject: dt-bindings: nvmem: imx-ocotp: add compatible string for imx6sll Add new compatible string for i.MX6SLL SOC. Signed-off-by: Anson Huang Acked-by: Rob Herring Signed-off-by: Srinivas Kandagatla Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/nvmem/imx-ocotp.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/nvmem/imx-ocotp.txt b/Documentation/devicetree/bindings/nvmem/imx-ocotp.txt index 729f6747813b..792bc5fafeb9 100644 --- a/Documentation/devicetree/bindings/nvmem/imx-ocotp.txt +++ b/Documentation/devicetree/bindings/nvmem/imx-ocotp.txt @@ -1,7 +1,7 @@ Freescale i.MX6 On-Chip OTP Controller (OCOTP) device tree bindings This binding represents the on-chip eFuse OTP controller found on -i.MX6Q/D, i.MX6DL/S, i.MX6SL, i.MX6SX and i.MX6UL SoCs. +i.MX6Q/D, i.MX6DL/S, i.MX6SL, i.MX6SX, i.MX6UL and i.MX6SLL SoCs. Required properties: - compatible: should be one of @@ -10,6 +10,7 @@ Required properties: "fsl,imx6sx-ocotp" (i.MX6SX), "fsl,imx6ul-ocotp" (i.MX6UL), "fsl,imx7d-ocotp" (i.MX7D/S), + "fsl,imx6sll-ocotp" (i.MX6SLL), followed by "syscon". - #address-cells : Should be 1 - #size-cells : Should be 1 -- cgit v1.2.3 From 6da27821a6f5d7648509a37d7d808323e2277431 Mon Sep 17 00:00:00 2001 From: Anson Huang Date: Wed, 11 Jul 2018 11:20:43 +0100 Subject: nvmem: imx-ocotp: add support for imx6sll i.MX6SLL is a new SoC of i.MX6 family, enable ocotp driver support for this SoC. Signed-off-by: Anson Huang Signed-off-by: Srinivas Kandagatla Signed-off-by: Greg Kroah-Hartman --- drivers/nvmem/imx-ocotp.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/nvmem/imx-ocotp.c b/drivers/nvmem/imx-ocotp.c index 60816c856dd6..afb429a417fe 100644 --- a/drivers/nvmem/imx-ocotp.c +++ b/drivers/nvmem/imx-ocotp.c @@ -409,6 +409,12 @@ static const struct ocotp_params imx6sl_params = { .set_timing = imx_ocotp_set_imx6_timing, }; +static const struct ocotp_params imx6sll_params = { + .nregs = 128, + .bank_address_words = 0, + .set_timing = imx_ocotp_set_imx6_timing, +}; + static const struct ocotp_params imx6sx_params = { .nregs = 128, .bank_address_words = 0, @@ -433,6 +439,7 @@ static const struct of_device_id imx_ocotp_dt_ids[] = { { .compatible = "fsl,imx6sx-ocotp", .data = &imx6sx_params }, { .compatible = "fsl,imx6ul-ocotp", .data = &imx6ul_params }, { .compatible = "fsl,imx7d-ocotp", .data = &imx7d_params }, + { .compatible = "fsl,imx6sll-ocotp", .data = &imx6sll_params }, { }, }; MODULE_DEVICE_TABLE(of, imx_ocotp_dt_ids); -- cgit v1.2.3 From 45cd74cb5061781e793a098c420a7f548fdc9e7d Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Tue, 10 Jul 2018 17:15:38 +0200 Subject: eventpoll.h: wrap casts in () properly When importing the latest copy of the kernel headers into Bionic, Christpher and Elliott noticed that the eventpoll.h casts were not wrapped in (). As it is, clang complains about macros without surrounding (), so this makes it a pain for userspace tools. So fix it up by adding another () pair, and make them line up purty by using tabs. Fixes: 65aaf87b3aa2 ("add EPOLLNVAL, annotate EPOLL... and event_poll->event") Reported-by: Christopher Ferris Reported-by: Elliott Hughes Cc: stable Cc: Thomas Gleixner Cc: Al Viro Signed-off-by: Greg Kroah-Hartman --- include/uapi/linux/eventpoll.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/uapi/linux/eventpoll.h b/include/uapi/linux/eventpoll.h index bf48e71f2634..8a3432d0f0dc 100644 --- a/include/uapi/linux/eventpoll.h +++ b/include/uapi/linux/eventpoll.h @@ -42,7 +42,7 @@ #define EPOLLRDHUP (__force __poll_t)0x00002000 /* Set exclusive wakeup mode for the target file descriptor */ -#define EPOLLEXCLUSIVE (__force __poll_t)(1U << 28) +#define EPOLLEXCLUSIVE ((__force __poll_t)(1U << 28)) /* * Request the handling of system wakeup events so as to prevent system suspends @@ -54,13 +54,13 @@ * * Requires CAP_BLOCK_SUSPEND */ -#define EPOLLWAKEUP (__force __poll_t)(1U << 29) +#define EPOLLWAKEUP ((__force __poll_t)(1U << 29)) /* Set the One Shot behaviour for the target file descriptor */ -#define EPOLLONESHOT (__force __poll_t)(1U << 30) +#define EPOLLONESHOT ((__force __poll_t)(1U << 30)) /* Set the Edge Triggered behaviour for the target file descriptor */ -#define EPOLLET (__force __poll_t)(1U << 31) +#define EPOLLET ((__force __poll_t)(1U << 31)) /* * On x86-64 make the 64bit structure have the same alignment as the -- cgit v1.2.3 From faa1a47388b33623e4d504c23569188907b039a0 Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Thu, 12 Jul 2018 22:29:55 +0100 Subject: parport: sunbpp: fix error return code Return an error code on failure. Change leading spaces to tab on the first if. Problem found using Coccinelle. Signed-off-by: Julia Lawall Signed-off-by: Sudip Mukherjee Signed-off-by: Greg Kroah-Hartman --- drivers/parport/parport_sunbpp.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/parport/parport_sunbpp.c b/drivers/parport/parport_sunbpp.c index 01cf1c1a841a..8de329546b82 100644 --- a/drivers/parport/parport_sunbpp.c +++ b/drivers/parport/parport_sunbpp.c @@ -286,12 +286,16 @@ static int bpp_probe(struct platform_device *op) ops = kmemdup(&parport_sunbpp_ops, sizeof(struct parport_operations), GFP_KERNEL); - if (!ops) + if (!ops) { + err = -ENOMEM; goto out_unmap; + } dprintk(("register_port\n")); - if (!(p = parport_register_port((unsigned long)base, irq, dma, ops))) + if (!(p = parport_register_port((unsigned long)base, irq, dma, ops))) { + err = -ENOMEM; goto out_free_ops; + } p->size = size; p->dev = &op->dev; -- cgit v1.2.3 From 34bcfdac8c92cc7971254ba5856afd12047c50b2 Mon Sep 17 00:00:00 2001 From: "Gustavo A. R. Silva" Date: Thu, 12 Jul 2018 22:29:56 +0100 Subject: parport: ieee1284: mark expected switch fall-throughs In preparation to enabling -Wimplicit-fallthrough, mark switch cases where we are expecting to fall through. Signed-off-by: Gustavo A. R. Silva Signed-off-by: Sudip Mukherjee Signed-off-by: Greg Kroah-Hartman --- drivers/parport/ieee1284.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/parport/ieee1284.c b/drivers/parport/ieee1284.c index 2d1a5c737c6e..f12b9da69255 100644 --- a/drivers/parport/ieee1284.c +++ b/drivers/parport/ieee1284.c @@ -267,7 +267,7 @@ static void parport_ieee1284_terminate (struct parport *port) port->ieee1284.phase = IEEE1284_PH_FWD_IDLE; } - /* fall-though.. */ + /* fall through */ default: /* Terminate from all other modes. */ @@ -615,6 +615,7 @@ ssize_t parport_write (struct parport *port, const void *buffer, size_t len) case IEEE1284_MODE_NIBBLE: case IEEE1284_MODE_BYTE: parport_negotiate (port, IEEE1284_MODE_COMPAT); + /* fall through */ case IEEE1284_MODE_COMPAT: DPRINTK (KERN_DEBUG "%s: Using compatibility mode\n", port->name); -- cgit v1.2.3 From 566208494e4d7cc7bdac48d4c9b99d4fcb8fca48 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Sat, 14 Jul 2018 17:33:32 +0100 Subject: vme: ca91cx42: remove redundant variable i Variable i is being assigned but is never used hence it is redundant and can be removed. Cleans up clang warning: warning: variable 'i' set but not used [-Wunused-but-set-variable] Signed-off-by: Colin Ian King Reviewed-by: Martyn Welch Signed-off-by: Greg Kroah-Hartman --- drivers/vme/bridges/vme_ca91cx42.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/vme/bridges/vme_ca91cx42.c b/drivers/vme/bridges/vme_ca91cx42.c index 5dd284008630..53bdc256805f 100644 --- a/drivers/vme/bridges/vme_ca91cx42.c +++ b/drivers/vme/bridges/vme_ca91cx42.c @@ -970,7 +970,6 @@ static unsigned int ca91cx42_master_rmw(struct vme_master_resource *image, { u32 result; uintptr_t pci_addr; - int i; struct ca91cx42_driver *bridge; struct device *dev; @@ -978,7 +977,6 @@ static unsigned int ca91cx42_master_rmw(struct vme_master_resource *image, dev = image->parent->parent; /* Find the PCI address that maps to the desired VME address */ - i = image->number; /* Locking as we can only do one of these at a time */ mutex_lock(&bridge->vme_rmw); -- cgit v1.2.3 From 8d62fe9499b8eb027837892aad8fb52d83a52a41 Mon Sep 17 00:00:00 2001 From: Tuomas Tynkkynen Date: Fri, 13 Jul 2018 00:39:56 +0300 Subject: virtio: virtconsole: Use seq_file for debugfs operations Simplifies the code and is more conventional to what's used in the rest of the kernel for debugfs ops. Signed-off-by: Tuomas Tynkkynen Reviewed-by: Amit Shah Signed-off-by: Greg Kroah-Hartman --- drivers/char/virtio_console.c | 60 ++++++++++++++++--------------------------- 1 file changed, 22 insertions(+), 38 deletions(-) diff --git a/drivers/char/virtio_console.c b/drivers/char/virtio_console.c index 17084cfcf53e..5b5b5d72eab7 100644 --- a/drivers/char/virtio_console.c +++ b/drivers/char/virtio_console.c @@ -1309,51 +1309,35 @@ static const struct attribute_group port_attribute_group = { .attrs = port_sysfs_entries, }; -static ssize_t debugfs_read(struct file *filp, char __user *ubuf, - size_t count, loff_t *offp) +static int debugfs_show(struct seq_file *s, void *data) { - struct port *port; - char *buf; - ssize_t ret, out_offset, out_count; + struct port *port = s->private; + + seq_printf(s, "name: %s\n", port->name ? port->name : ""); + seq_printf(s, "guest_connected: %d\n", port->guest_connected); + seq_printf(s, "host_connected: %d\n", port->host_connected); + seq_printf(s, "outvq_full: %d\n", port->outvq_full); + seq_printf(s, "bytes_sent: %lu\n", port->stats.bytes_sent); + seq_printf(s, "bytes_received: %lu\n", port->stats.bytes_received); + seq_printf(s, "bytes_discarded: %lu\n", port->stats.bytes_discarded); + seq_printf(s, "is_console: %s\n", + is_console_port(port) ? "yes" : "no"); + seq_printf(s, "console_vtermno: %u\n", port->cons.vtermno); - out_count = 1024; - buf = kmalloc(out_count, GFP_KERNEL); - if (!buf) - return -ENOMEM; + return 0; +} - port = filp->private_data; - out_offset = 0; - out_offset += snprintf(buf + out_offset, out_count, - "name: %s\n", port->name ? port->name : ""); - out_offset += snprintf(buf + out_offset, out_count - out_offset, - "guest_connected: %d\n", port->guest_connected); - out_offset += snprintf(buf + out_offset, out_count - out_offset, - "host_connected: %d\n", port->host_connected); - out_offset += snprintf(buf + out_offset, out_count - out_offset, - "outvq_full: %d\n", port->outvq_full); - out_offset += snprintf(buf + out_offset, out_count - out_offset, - "bytes_sent: %lu\n", port->stats.bytes_sent); - out_offset += snprintf(buf + out_offset, out_count - out_offset, - "bytes_received: %lu\n", - port->stats.bytes_received); - out_offset += snprintf(buf + out_offset, out_count - out_offset, - "bytes_discarded: %lu\n", - port->stats.bytes_discarded); - out_offset += snprintf(buf + out_offset, out_count - out_offset, - "is_console: %s\n", - is_console_port(port) ? "yes" : "no"); - out_offset += snprintf(buf + out_offset, out_count - out_offset, - "console_vtermno: %u\n", port->cons.vtermno); - - ret = simple_read_from_buffer(ubuf, count, offp, buf, out_offset); - kfree(buf); - return ret; +static int debugfs_open(struct inode *inode, struct file *file) +{ + return single_open(file, debugfs_show, inode->i_private); } static const struct file_operations port_debugfs_ops = { .owner = THIS_MODULE, - .open = simple_open, - .read = debugfs_read, + .open = debugfs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, }; static void set_console_size(struct port *port, u16 rows, u16 cols) -- cgit v1.2.3 From 55570f1a441787f67efe1aef3b5deae431ca7ee9 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Mon, 16 Jul 2018 12:42:02 +0200 Subject: gnss: ubx: make struct ubx_gserial_ops static The structure ubx_gserial_ops is local to the source and does not need to be in global scope, so make it static. Cleans up sparse warning: symbol 'ubx_gserial_ops' was not declared. Should it be static? Signed-off-by: Colin Ian King Signed-off-by: Johan Hovold Signed-off-by: Greg Kroah-Hartman --- drivers/gnss/ubx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gnss/ubx.c b/drivers/gnss/ubx.c index 902b6854b7db..12568aebb7f6 100644 --- a/drivers/gnss/ubx.c +++ b/drivers/gnss/ubx.c @@ -59,7 +59,7 @@ static int ubx_set_power(struct gnss_serial *gserial, return -EINVAL; } -const struct gnss_serial_ops ubx_gserial_ops = { +static const struct gnss_serial_ops ubx_gserial_ops = { .set_power = ubx_set_power, }; -- cgit v1.2.3 From d9995a0fab40af333b08902ad43a387843ca0e17 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Mon, 16 Jul 2018 12:42:03 +0200 Subject: gnss: fix potential error pointer dereference The gnss_allocate_device() function returns a mix of NULL and error pointers on error. It should only return one or the other. Since the callers both check for NULL, I've modified it to return NULL on error. Fixes: 2b6a44035143 ("gnss: add GNSS receiver subsystem") Signed-off-by: Dan Carpenter Signed-off-by: Johan Hovold Signed-off-by: Greg Kroah-Hartman --- drivers/gnss/core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gnss/core.c b/drivers/gnss/core.c index f30ef8338b3a..4291a0dd22aa 100644 --- a/drivers/gnss/core.c +++ b/drivers/gnss/core.c @@ -235,7 +235,7 @@ struct gnss_device *gnss_allocate_device(struct device *parent) id = ida_simple_get(&gnss_minors, 0, GNSS_MINORS, GFP_KERNEL); if (id < 0) { kfree(gdev); - return ERR_PTR(id); + return NULL; } gdev->id = id; @@ -270,7 +270,7 @@ struct gnss_device *gnss_allocate_device(struct device *parent) err_put_device: put_device(dev); - return ERR_PTR(-ENOMEM); + return NULL; } EXPORT_SYMBOL_GPL(gnss_allocate_device); -- cgit v1.2.3 From 7f3838fb8c03d8ef52792ac3654a0b5f79a673e5 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Sun, 15 Jul 2018 14:41:18 +0300 Subject: mei: bus: suppress sign-compare warnings Comparison between signed and unsigned warnings and associated type promotion may cause error condition not be detected. The type promotion issue in mei bus was addressed by two patches: commit b40b3e9358fb ("mei: bus: type promotion bug in mei_nfc_if_version()") commit cf1ed2c59b98 ("mei: bus: type promotion bug in mei_fwver()") Now it is possible to suppress the warning, by adding proper casting to move out of radar. Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/bus-fixup.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/misc/mei/bus-fixup.c b/drivers/misc/mei/bus-fixup.c index 2b8390447117..a6f41f96f2a1 100644 --- a/drivers/misc/mei/bus-fixup.c +++ b/drivers/misc/mei/bus-fixup.c @@ -181,7 +181,7 @@ static int mei_fwver(struct mei_cl_device *cldev) ret = 0; bytes_recv = __mei_cl_recv(cldev->cl, buf, sizeof(buf), 0, MKHI_RCV_TIMEOUT); - if (bytes_recv < 0 || bytes_recv < MKHI_FWVER_LEN(1)) { + if (bytes_recv < 0 || (size_t)bytes_recv < MKHI_FWVER_LEN(1)) { /* * Should be at least one version block, * error out if nothing found @@ -193,7 +193,7 @@ static int mei_fwver(struct mei_cl_device *cldev) fwver = (struct mkhi_fw_ver *)req->data; memset(cldev->bus->fw_ver, 0, sizeof(cldev->bus->fw_ver)); for (i = 0; i < MEI_MAX_FW_VER_BLOCKS; i++) { - if (bytes_recv < MKHI_FWVER_LEN(i + 1)) + if ((size_t)bytes_recv < MKHI_FWVER_LEN(i + 1)) break; dev_dbg(&cldev->dev, "FW version%d %d:%d.%d.%d.%d\n", i, fwver->ver[i].platform, @@ -341,7 +341,7 @@ static int mei_nfc_if_version(struct mei_cl *cl, ret = 0; bytes_recv = __mei_cl_recv(cl, (u8 *)reply, if_version_length, 0, 0); - if (bytes_recv < 0 || bytes_recv < if_version_length) { + if (bytes_recv < 0 || (size_t)bytes_recv < if_version_length) { dev_err(bus->dev, "Could not read IF version\n"); ret = -EIO; goto err; -- cgit v1.2.3 From 3772e5da445420543b25825ac2b5971f3743f6e8 Mon Sep 17 00:00:00 2001 From: Robert Lippert Date: Fri, 6 Jul 2018 11:25:32 -0700 Subject: drivers/misc: Aspeed LPC snoop output using misc chardev Provides the data bytes snooped over the LPC snoop bus to userspace as a (blocking) misc character device. Bytes output from the host using LPC I/O transactions to the snooped port can be watched or retrieved from the character device using a simple command like this: ~# od -w1 -A n -t x1 /dev/aspeed-lpc-snoop0 10 de ad c0 ff ee Signed-off-by: Robert Lippert Signed-off-by: Patrick Venture Signed-off-by: Benjamin Fair Signed-off-by: Greg Kroah-Hartman --- drivers/misc/aspeed-lpc-snoop.c | 84 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 74 insertions(+), 10 deletions(-) diff --git a/drivers/misc/aspeed-lpc-snoop.c b/drivers/misc/aspeed-lpc-snoop.c index cb78c98bc78d..2feb4347d67f 100644 --- a/drivers/misc/aspeed-lpc-snoop.c +++ b/drivers/misc/aspeed-lpc-snoop.c @@ -16,12 +16,15 @@ #include #include +#include #include #include +#include #include #include #include #include +#include #include #define DEVICE_NAME "aspeed-lpc-snoop" @@ -59,20 +62,70 @@ struct aspeed_lpc_snoop_model_data { unsigned int has_hicrb_ensnp; }; +struct aspeed_lpc_snoop_channel { + struct kfifo fifo; + wait_queue_head_t wq; + struct miscdevice miscdev; +}; + struct aspeed_lpc_snoop { struct regmap *regmap; int irq; - struct kfifo snoop_fifo[NUM_SNOOP_CHANNELS]; + struct aspeed_lpc_snoop_channel chan[NUM_SNOOP_CHANNELS]; +}; + +static struct aspeed_lpc_snoop_channel *snoop_file_to_chan(struct file *file) +{ + return container_of(file->private_data, + struct aspeed_lpc_snoop_channel, + miscdev); +} + +static ssize_t snoop_file_read(struct file *file, char __user *buffer, + size_t count, loff_t *ppos) +{ + struct aspeed_lpc_snoop_channel *chan = snoop_file_to_chan(file); + unsigned int copied; + int ret = 0; + + if (kfifo_is_empty(&chan->fifo)) { + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + ret = wait_event_interruptible(chan->wq, + !kfifo_is_empty(&chan->fifo)); + if (ret == -ERESTARTSYS) + return -EINTR; + } + ret = kfifo_to_user(&chan->fifo, buffer, count, &copied); + + return ret ? ret : copied; +} + +static unsigned int snoop_file_poll(struct file *file, + struct poll_table_struct *pt) +{ + struct aspeed_lpc_snoop_channel *chan = snoop_file_to_chan(file); + + poll_wait(file, &chan->wq, pt); + return !kfifo_is_empty(&chan->fifo) ? POLLIN : 0; +} + +static const struct file_operations snoop_fops = { + .owner = THIS_MODULE, + .read = snoop_file_read, + .poll = snoop_file_poll, + .llseek = noop_llseek, }; /* Save a byte to a FIFO and discard the oldest byte if FIFO is full */ -static void put_fifo_with_discard(struct kfifo *fifo, u8 val) +static void put_fifo_with_discard(struct aspeed_lpc_snoop_channel *chan, u8 val) { - if (!kfifo_initialized(fifo)) + if (!kfifo_initialized(&chan->fifo)) return; - if (kfifo_is_full(fifo)) - kfifo_skip(fifo); - kfifo_put(fifo, val); + if (kfifo_is_full(&chan->fifo)) + kfifo_skip(&chan->fifo); + kfifo_put(&chan->fifo, val); + wake_up_interruptible(&chan->wq); } static irqreturn_t aspeed_lpc_snoop_irq(int irq, void *arg) @@ -97,12 +150,12 @@ static irqreturn_t aspeed_lpc_snoop_irq(int irq, void *arg) if (reg & HICR6_STR_SNP0W) { u8 val = (data & SNPWDR_CH0_MASK) >> SNPWDR_CH0_SHIFT; - put_fifo_with_discard(&lpc_snoop->snoop_fifo[0], val); + put_fifo_with_discard(&lpc_snoop->chan[0], val); } if (reg & HICR6_STR_SNP1W) { u8 val = (data & SNPWDR_CH1_MASK) >> SNPWDR_CH1_SHIFT; - put_fifo_with_discard(&lpc_snoop->snoop_fifo[1], val); + put_fifo_with_discard(&lpc_snoop->chan[1], val); } return IRQ_HANDLED; @@ -139,12 +192,22 @@ static int aspeed_lpc_enable_snoop(struct aspeed_lpc_snoop *lpc_snoop, const struct aspeed_lpc_snoop_model_data *model_data = of_device_get_match_data(dev); + init_waitqueue_head(&lpc_snoop->chan[channel].wq); /* Create FIFO datastructure */ - rc = kfifo_alloc(&lpc_snoop->snoop_fifo[channel], + rc = kfifo_alloc(&lpc_snoop->chan[channel].fifo, SNOOP_FIFO_SIZE, GFP_KERNEL); if (rc) return rc; + lpc_snoop->chan[channel].miscdev.minor = MISC_DYNAMIC_MINOR; + lpc_snoop->chan[channel].miscdev.name = + devm_kasprintf(dev, GFP_KERNEL, "%s%d", DEVICE_NAME, channel); + lpc_snoop->chan[channel].miscdev.fops = &snoop_fops; + lpc_snoop->chan[channel].miscdev.parent = dev; + rc = misc_register(&lpc_snoop->chan[channel].miscdev); + if (rc) + return rc; + /* Enable LPC snoop channel at requested port */ switch (channel) { case 0: @@ -191,7 +254,8 @@ static void aspeed_lpc_disable_snoop(struct aspeed_lpc_snoop *lpc_snoop, return; } - kfifo_free(&lpc_snoop->snoop_fifo[channel]); + kfifo_free(&lpc_snoop->chan[channel].fifo); + misc_deregister(&lpc_snoop->chan[channel].miscdev); } static int aspeed_lpc_snoop_probe(struct platform_device *pdev) -- cgit v1.2.3 From 555e7943f2818391c12aaa4b721b4f5ebb1fd036 Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Wed, 11 Jul 2018 16:41:10 +0100 Subject: misc: vexpress/syscfg: Use devm_ioremap_resource() to map memory Instead of checking the return value of platform_get_resource(), we can use devm_ioremap_resource() which has the NULL pointer check and the memory region requesting. devm_ioremap_resource is designed to replace calls to devm_request_mem_region followed by devm_ioremap, so let's use the same. Cc: Lorenzo Pieralisi Signed-off-by: Sudeep Holla Acked-by: Liviu Dudau Signed-off-by: Greg Kroah-Hartman --- drivers/misc/vexpress-syscfg.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/drivers/misc/vexpress-syscfg.c b/drivers/misc/vexpress-syscfg.c index 80a6f199077c..6c3591cdf855 100644 --- a/drivers/misc/vexpress-syscfg.c +++ b/drivers/misc/vexpress-syscfg.c @@ -258,13 +258,9 @@ static int vexpress_syscfg_probe(struct platform_device *pdev) INIT_LIST_HEAD(&syscfg->funcs); res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!devm_request_mem_region(&pdev->dev, res->start, - resource_size(res), pdev->name)) - return -EBUSY; - - syscfg->base = devm_ioremap(&pdev->dev, res->start, resource_size(res)); - if (!syscfg->base) - return -EFAULT; + syscfg->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(syscfg->base)) + return PTR_ERR(syscfg->base); /* Must use dev.parent (MFD), as that's where DT phandle points at... */ bridge = vexpress_config_bridge_register(pdev->dev.parent, -- cgit v1.2.3 From fbb5858c1467ef8feccc910ac9e9f529186a9f9d Mon Sep 17 00:00:00 2001 From: Stephen Hemminger Date: Fri, 20 Jul 2018 15:11:26 -0700 Subject: uio: add SPDX license tag Replace short statement in comment with proper SPDX license tag. Signed-off-by: Stephen Hemminger Signed-off-by: Greg Kroah-Hartman --- drivers/uio/uio.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/uio/uio.c b/drivers/uio/uio.c index 5d421d7e8904..f63967c8e95a 100644 --- a/drivers/uio/uio.c +++ b/drivers/uio/uio.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0 /* * drivers/uio/uio.c * @@ -9,8 +10,6 @@ * Userspace IO * * Base Functions - * - * Licensed under the GPLv2 only. */ #include -- cgit v1.2.3 From e7de2590f18a272e63732b9d519250d1b522b2c4 Mon Sep 17 00:00:00 2001 From: Hailong Liu Date: Fri, 20 Jul 2018 08:31:56 +0800 Subject: uio: fix wrong return value from uio_mmap() uio_mmap has multiple fail paths to set return value to nonzero then goto out. However, it always returns *0* from the *out* at end, and this will mislead callers who check the return value of this function. Fixes: 57c5f4df0a5a0ee ("uio: fix crash after the device is unregistered") CC: Xiubo Li Signed-off-by: Hailong Liu Cc: stable Signed-off-by: Jiang Biao Signed-off-by: Greg Kroah-Hartman --- drivers/uio/uio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/uio/uio.c b/drivers/uio/uio.c index f63967c8e95a..144cf7365288 100644 --- a/drivers/uio/uio.c +++ b/drivers/uio/uio.c @@ -813,7 +813,7 @@ static int uio_mmap(struct file *filep, struct vm_area_struct *vma) out: mutex_unlock(&idev->info_lock); - return 0; + return ret; } static const struct file_operations uio_fops = { -- cgit v1.2.3 From bce5c2ea350f5a57353295534faba00b28cadf14 Mon Sep 17 00:00:00 2001 From: Stephen Hemminger Date: Sat, 21 Jul 2018 06:31:39 -0700 Subject: uio: add SPDX license tags For those without any license text present or short reference to GPL, add SPDX tag. Signed-off-by: Stephen Hemminger Signed-off-by: Greg Kroah-Hartman --- drivers/uio/uio_cif.c | 4 +--- drivers/uio/uio_fsl_elbc_gpcm.c | 1 + drivers/uio/uio_hv_generic.c | 4 +--- drivers/uio/uio_netx.c | 3 +-- drivers/uio/uio_pci_generic.c | 3 +-- drivers/uio/uio_sercos3.c | 1 + 6 files changed, 6 insertions(+), 10 deletions(-) diff --git a/drivers/uio/uio_cif.c b/drivers/uio/uio_cif.c index 30f533ce3758..ab60186f9759 100644 --- a/drivers/uio/uio_cif.c +++ b/drivers/uio/uio_cif.c @@ -1,11 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0 /* * UIO Hilscher CIF card driver * * (C) 2007 Hans J. Koch * Original code (C) 2005 Benedikt Spranger - * - * Licensed under GPL version 2 only. - * */ #include diff --git a/drivers/uio/uio_fsl_elbc_gpcm.c b/drivers/uio/uio_fsl_elbc_gpcm.c index b55191335d90..bbc17effae5e 100644 --- a/drivers/uio/uio_fsl_elbc_gpcm.c +++ b/drivers/uio/uio_fsl_elbc_gpcm.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0 /* uio_fsl_elbc_gpcm: UIO driver for eLBC/GPCM peripherals Copyright (C) 2014 Linutronix GmbH diff --git a/drivers/uio/uio_hv_generic.c b/drivers/uio/uio_hv_generic.c index c690d100adcd..e401be8321ab 100644 --- a/drivers/uio/uio_hv_generic.c +++ b/drivers/uio/uio_hv_generic.c @@ -1,12 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0 /* * uio_hv_generic - generic UIO driver for VMBus * * Copyright (c) 2013-2016 Brocade Communications Systems, Inc. * Copyright (c) 2016, Microsoft Corporation. * - * - * This work is licensed under the terms of the GNU GPL, version 2. - * * Since the driver does not declare any device ids, you must allocate * id and bind the device to the driver yourself. For example: * diff --git a/drivers/uio/uio_netx.c b/drivers/uio/uio_netx.c index 4c345db8b016..9ae29ffde410 100644 --- a/drivers/uio/uio_netx.c +++ b/drivers/uio/uio_netx.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0 /* * UIO driver for Hilscher NetX based fieldbus cards (cifX, comX). * See http://www.hilscher.com for details. @@ -5,8 +6,6 @@ * (C) 2007 Hans J. Koch * (C) 2008 Manuel Traut * - * Licensed under GPL version 2 only. - * */ #include diff --git a/drivers/uio/uio_pci_generic.c b/drivers/uio/uio_pci_generic.c index a56fdf972dbe..8773e373ffe5 100644 --- a/drivers/uio/uio_pci_generic.c +++ b/drivers/uio/uio_pci_generic.c @@ -1,10 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0 /* uio_pci_generic - generic UIO driver for PCI 2.3 devices * * Copyright (C) 2009 Red Hat, Inc. * Author: Michael S. Tsirkin * - * This work is licensed under the terms of the GNU GPL, version 2. - * * Since the driver does not declare any device ids, you must allocate * id and bind the device to the driver yourself. For example: * diff --git a/drivers/uio/uio_sercos3.c b/drivers/uio/uio_sercos3.c index 9cfdfcafa262..9658a0887fee 100644 --- a/drivers/uio/uio_sercos3.c +++ b/drivers/uio/uio_sercos3.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0 /* sercos3: UIO driver for the Automata Sercos III PCI card Copyright (C) 2008 Linutronix GmbH -- cgit v1.2.3 From d5e838275c80aeb96d4c0b81442a851dfef2e948 Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Tue, 5 Jun 2018 13:21:26 +1000 Subject: devres: Add devm_of_iomap() There are still quite a few cases where a device might want to get to a different node of the device-tree, obtain the resources and map them. We have of_iomap() and of_io_request_and_map() but they both have shortcomings, such as not returning the size of the resource found (which can be useful) and not being "managed". This adds a devm_of_iomap() that provides all of these and should probably replace uses of the above in most drivers. Signed-off-by: Benjamin Herrenschmidt Reviewed-by: Linus Walleij Reviewed-by: Joel Stanley --- include/linux/device.h | 4 ++++ lib/devres.c | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/include/linux/device.h b/include/linux/device.h index 055a69dbcd18..6aa8d51eabe9 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -696,6 +696,10 @@ extern void devm_free_pages(struct device *dev, unsigned long addr); void __iomem *devm_ioremap_resource(struct device *dev, struct resource *res); +void __iomem *devm_of_iomap(struct device *dev, + struct device_node *node, int index, + resource_size_t *size); + /* allows to add/remove a custom action to devres stack */ int devm_add_action(struct device *dev, void (*action)(void *), void *data); void devm_remove_action(struct device *dev, void (*action)(void *), void *data); diff --git a/lib/devres.c b/lib/devres.c index 5bec1120b392..faccf1a037d0 100644 --- a/lib/devres.c +++ b/lib/devres.c @@ -4,6 +4,7 @@ #include #include #include +#include enum devm_ioremap_type { DEVM_IOREMAP = 0, @@ -162,6 +163,41 @@ void __iomem *devm_ioremap_resource(struct device *dev, struct resource *res) } EXPORT_SYMBOL(devm_ioremap_resource); +/* + * devm_of_iomap - Requests a resource and maps the memory mapped IO + * for a given device_node managed by a given device + * + * Checks that a resource is a valid memory region, requests the memory + * region and ioremaps it. All operations are managed and will be undone + * on driver detach of the device. + * + * This is to be used when a device requests/maps resources described + * by other device tree nodes (children or otherwise). + * + * @dev: The device "managing" the resource + * @node: The device-tree node where the resource resides + * @index: index of the MMIO range in the "reg" property + * @size: Returns the size of the resource (pass NULL if not needed) + * Returns a pointer to the requested and mapped memory or an ERR_PTR() encoded + * error code on failure. Usage example: + * + * base = devm_of_iomap(&pdev->dev, node, 0, NULL); + * if (IS_ERR(base)) + * return PTR_ERR(base); + */ +void __iomem *devm_of_iomap(struct device *dev, struct device_node *node, int index, + resource_size_t *size) +{ + struct resource res; + + if (of_address_to_resource(node, index, &res)) + return IOMEM_ERR_PTR(-EINVAL); + if (size) + *size = resource_size(&res); + return devm_ioremap_resource(dev, &res); +} +EXPORT_SYMBOL(devm_of_iomap); + #ifdef CONFIG_HAS_IOPORT_MAP /* * Generic iomap devres -- cgit v1.2.3 From 2be5263c5df45bbd5ad49ea8c394aca6e709d204 Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Wed, 13 Jun 2018 21:28:38 +1000 Subject: dt-bindings: fsi: Document binding for the fsi-master-ast-cf "device" This isn't per-se a real device, it's a pseudo-device that represents the use of the Aspeed built-in ColdFire to implement the FSI protocol by bitbanging the GPIOs instead of doing it from the ARM core. Thus it's a drop-in replacement for the existing fsi-master-gpio pseudo-device for use on systems based on the Aspeed chips. It has most of the same properties, plus some more needed to operate the coprocessor. Signed-off-by: Benjamin Herrenschmidt Reviewed-by: Rob Herring --- .../devicetree/bindings/fsi/fsi-master-ast-cf.txt | 36 ++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 Documentation/devicetree/bindings/fsi/fsi-master-ast-cf.txt diff --git a/Documentation/devicetree/bindings/fsi/fsi-master-ast-cf.txt b/Documentation/devicetree/bindings/fsi/fsi-master-ast-cf.txt new file mode 100644 index 000000000000..3dc752db748b --- /dev/null +++ b/Documentation/devicetree/bindings/fsi/fsi-master-ast-cf.txt @@ -0,0 +1,36 @@ +Device-tree bindings for ColdFire offloaded gpio-based FSI master driver +------------------------------------------------------------------------ + +Required properties: + - compatible = + "aspeed,ast2400-cf-fsi-master" for an AST2400 based system + or + "aspeed,ast2500-cf-fsi-master" for an AST2500 based system + + - clock-gpios = ; : GPIO for FSI clock + - data-gpios = ; : GPIO for FSI data signal + - enable-gpios = ; : GPIO for enable signal + - trans-gpios = ; : GPIO for voltage translator enable + - mux-gpios = ; : GPIO for pin multiplexing with other + functions (eg, external FSI masters) + - memory-region = ; : Reference to the reserved memory for + the ColdFire. Must be 2M aligned on + AST2400 and 1M aligned on AST2500 + - aspeed,sram = ; : Reference to the SRAM node. + - aspeed,cvic = ; : Reference to the CVIC node. + +Examples: + + fsi-master { + compatible = "aspeed,ast2500-cf-fsi-master", "fsi-master"; + + clock-gpios = <&gpio 0>; + data-gpios = <&gpio 1>; + enable-gpios = <&gpio 2>; + trans-gpios = <&gpio 3>; + mux-gpios = <&gpio 4>; + + memory-region = <&coldfire_memory>; + aspeed,sram = <&sram>; + aspeed,cvic = <&cvic>; + } -- cgit v1.2.3 From 6a794a27daca9c5a39de13c03b0748bb2d4a7a70 Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Tue, 12 Jun 2018 09:55:04 +1000 Subject: fsi: master-ast-cf: Add new FSI master using Aspeed ColdFire The Aspeed AST2x00 can contain a ColdFire v1 coprocessor which is currently unused on OpenPower systems. This adds an alternative to the fsi-master-gpio driver that uses that coprocessor instead of bit banging from the ARM core itself. The end result is about 4 times faster. The firmware for the coprocessor and its source code can be found at https://github.com/ozbenh/cf-fsi and is system specific. Signed-off-by: Benjamin Herrenschmidt --- drivers/fsi/Kconfig | 9 + drivers/fsi/Makefile | 1 + drivers/fsi/cf-fsi-fw.h | 157 ++++ drivers/fsi/fsi-master-ast-cf.c | 1438 ++++++++++++++++++++++++++++++ include/trace/events/fsi_master_ast_cf.h | 150 ++++ 5 files changed, 1755 insertions(+) create mode 100644 drivers/fsi/cf-fsi-fw.h create mode 100644 drivers/fsi/fsi-master-ast-cf.c create mode 100644 include/trace/events/fsi_master_ast_cf.h diff --git a/drivers/fsi/Kconfig b/drivers/fsi/Kconfig index 9c08f467a7bb..8d82b1e60514 100644 --- a/drivers/fsi/Kconfig +++ b/drivers/fsi/Kconfig @@ -27,6 +27,15 @@ config FSI_MASTER_HUB allow chaining of FSI links to an arbitrary depth. This allows for a high target device fanout. +config FSI_MASTER_AST_CF + tristate "FSI master based on Aspeed ColdFire coprocessor" + depends on GPIOLIB + depends on GPIO_ASPEED + ---help--- + This option enables a FSI master using the AST2400 and AST2500 GPIO + lines driven by the internal ColdFire coprocessor. This requires + the corresponding machine specific ColdFire firmware to be available. + config FSI_SCOM tristate "SCOM FSI client device driver" ---help--- diff --git a/drivers/fsi/Makefile b/drivers/fsi/Makefile index 851182e1cd9e..a50d6ce22fb3 100644 --- a/drivers/fsi/Makefile +++ b/drivers/fsi/Makefile @@ -2,5 +2,6 @@ obj-$(CONFIG_FSI) += fsi-core.o obj-$(CONFIG_FSI_MASTER_HUB) += fsi-master-hub.o obj-$(CONFIG_FSI_MASTER_GPIO) += fsi-master-gpio.o +obj-$(CONFIG_FSI_MASTER_AST_CF) += fsi-master-ast-cf.o obj-$(CONFIG_FSI_SCOM) += fsi-scom.o obj-$(CONFIG_FSI_SBEFIFO) += fsi-sbefifo.o diff --git a/drivers/fsi/cf-fsi-fw.h b/drivers/fsi/cf-fsi-fw.h new file mode 100644 index 000000000000..712df0461911 --- /dev/null +++ b/drivers/fsi/cf-fsi-fw.h @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: GPL-2.0+ +#ifndef __CF_FSI_FW_H +#define __CF_FSI_FW_H + +/* + * uCode file layout + * + * 0000...03ff : m68k exception vectors + * 0400...04ff : Header info & boot config block + * 0500....... : Code & stack + */ + +/* + * Header info & boot config area + * + * The Header info is built into the ucode and provide version and + * platform information. + * + * the Boot config needs to be adjusted by the ARM prior to starting + * the ucode if the Command/Status area isn't at 0x320000 in CF space + * (ie. beginning of SRAM). + */ + +#define HDR_OFFSET 0x400 + +/* Info: Signature & version */ +#define HDR_SYS_SIG 0x00 /* 2 bytes system signature */ +#define SYS_SIG_SHARED 0x5348 +#define SYS_SIG_SPLIT 0x5350 +#define HDR_FW_VERS 0x02 /* 2 bytes Major.Minor */ +#define HDR_API_VERS 0x04 /* 2 bytes Major.Minor */ +#define API_VERSION_MAJ 2 /* Current version */ +#define API_VERSION_MIN 1 +#define HDR_FW_OPTIONS 0x08 /* 4 bytes option flags */ +#define FW_OPTION_TRACE_EN 0x00000001 /* FW tracing enabled */ +#define FW_OPTION_CONT_CLOCK 0x00000002 /* Continuous clocking supported */ +#define HDR_FW_SIZE 0x10 /* 4 bytes size for combo image */ + +/* Boot Config: Address of Command/Status area */ +#define HDR_CMD_STAT_AREA 0x80 /* 4 bytes CF address */ +#define HDR_FW_CONTROL 0x84 /* 4 bytes control flags */ +#define FW_CONTROL_CONT_CLOCK 0x00000002 /* Continuous clocking enabled */ +#define FW_CONTROL_DUMMY_RD 0x00000004 /* Extra dummy read (AST2400) */ +#define FW_CONTROL_USE_STOP 0x00000008 /* Use STOP instructions */ +#define HDR_CLOCK_GPIO_VADDR 0x90 /* 2 bytes offset from GPIO base */ +#define HDR_CLOCK_GPIO_DADDR 0x92 /* 2 bytes offset from GPIO base */ +#define HDR_DATA_GPIO_VADDR 0x94 /* 2 bytes offset from GPIO base */ +#define HDR_DATA_GPIO_DADDR 0x96 /* 2 bytes offset from GPIO base */ +#define HDR_TRANS_GPIO_VADDR 0x98 /* 2 bytes offset from GPIO base */ +#define HDR_TRANS_GPIO_DADDR 0x9a /* 2 bytes offset from GPIO base */ +#define HDR_CLOCK_GPIO_BIT 0x9c /* 1 byte bit number */ +#define HDR_DATA_GPIO_BIT 0x9d /* 1 byte bit number */ +#define HDR_TRANS_GPIO_BIT 0x9e /* 1 byte bit number */ + +/* + * Command/Status area layout: Main part + */ + +/* Command/Status register: + * + * +---------------------------+ + * | STAT | RLEN | CLEN | CMD | + * | 8 | 8 | 8 | 8 | + * +---------------------------+ + * | | | | + * status | | | + * Response len | | + * (in bits) | | + * | | + * Command len | + * (in bits) | + * | + * Command code + * + * Due to the big endian layout, that means that a byte read will + * return the status byte + */ +#define CMD_STAT_REG 0x00 +#define CMD_REG_CMD_MASK 0x000000ff +#define CMD_REG_CMD_SHIFT 0 +#define CMD_NONE 0x00 +#define CMD_COMMAND 0x01 +#define CMD_BREAK 0x02 +#define CMD_IDLE_CLOCKS 0x03 /* clen = #clocks */ +#define CMD_INVALID 0xff +#define CMD_REG_CLEN_MASK 0x0000ff00 +#define CMD_REG_CLEN_SHIFT 8 +#define CMD_REG_RLEN_MASK 0x00ff0000 +#define CMD_REG_RLEN_SHIFT 16 +#define CMD_REG_STAT_MASK 0xff000000 +#define CMD_REG_STAT_SHIFT 24 +#define STAT_WORKING 0x00 +#define STAT_COMPLETE 0x01 +#define STAT_ERR_INVAL_CMD 0x80 +#define STAT_ERR_INVAL_IRQ 0x81 +#define STAT_ERR_MTOE 0x82 + +/* Response tag & CRC */ +#define STAT_RTAG 0x04 + +/* Response CRC */ +#define STAT_RCRC 0x05 + +/* Echo and Send delay */ +#define ECHO_DLY_REG 0x08 +#define SEND_DLY_REG 0x09 + +/* Command data area + * + * Last byte of message must be left aligned + */ +#define CMD_DATA 0x10 /* 64 bit of data */ + +/* Response data area, right aligned, unused top bits are 1 */ +#define RSP_DATA 0x20 /* 32 bit of data */ + +/* Misc */ +#define INT_CNT 0x30 /* 32-bit interrupt count */ +#define BAD_INT_VEC 0x34 /* 32-bit bad interrupt vector # */ +#define CF_STARTED 0x38 /* byte, set to -1 when copro started */ +#define CLK_CNT 0x3c /* 32-bit, clock count (debug only) */ + +/* + * SRAM layout: GPIO arbitration part + */ +#define ARB_REG 0x40 +#define ARB_ARM_REQ 0x01 +#define ARB_ARM_ACK 0x02 + +/* Misc2 */ +#define CF_RESET_D0 0x50 +#define CF_RESET_D1 0x54 +#define BAD_INT_S0 0x58 +#define BAD_INT_S1 0x5c +#define STOP_CNT 0x60 + +/* Internal */ + +/* + * SRAM layout: Trace buffer (debug builds only) + */ +#define TRACEBUF 0x100 +#define TR_CLKOBIT0 0xc0 +#define TR_CLKOBIT1 0xc1 +#define TR_CLKOSTART 0x82 +#define TR_OLEN 0x83 /* + len */ +#define TR_CLKZ 0x84 /* + count */ +#define TR_CLKWSTART 0x85 +#define TR_CLKTAG 0x86 /* + tag */ +#define TR_CLKDATA 0x87 /* + len */ +#define TR_CLKCRC 0x88 /* + raw crc */ +#define TR_CLKIBIT0 0x90 +#define TR_CLKIBIT1 0x91 +#define TR_END 0xff + +#endif /* __CF_FSI_FW_H */ + diff --git a/drivers/fsi/fsi-master-ast-cf.c b/drivers/fsi/fsi-master-ast-cf.c new file mode 100644 index 000000000000..57afaae0b691 --- /dev/null +++ b/drivers/fsi/fsi-master-ast-cf.c @@ -0,0 +1,1438 @@ +// SPDX-License-Identifier: GPL-2.0+ +// Copyright 2018 IBM Corp +/* + * A FSI master controller, using a simple GPIO bit-banging interface + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fsi-master.h" +#include "cf-fsi-fw.h" + +#define FW_FILE_NAME "cf-fsi-fw.bin" + +/* Common SCU based coprocessor control registers */ +#define SCU_COPRO_CTRL 0x100 +#define SCU_COPRO_RESET 0x00000002 +#define SCU_COPRO_CLK_EN 0x00000001 + +/* AST2500 specific ones */ +#define SCU_2500_COPRO_SEG0 0x104 +#define SCU_2500_COPRO_SEG1 0x108 +#define SCU_2500_COPRO_SEG2 0x10c +#define SCU_2500_COPRO_SEG3 0x110 +#define SCU_2500_COPRO_SEG4 0x114 +#define SCU_2500_COPRO_SEG5 0x118 +#define SCU_2500_COPRO_SEG6 0x11c +#define SCU_2500_COPRO_SEG7 0x120 +#define SCU_2500_COPRO_SEG8 0x124 +#define SCU_2500_COPRO_SEG_SWAP 0x00000001 +#define SCU_2500_COPRO_CACHE_CTL 0x128 +#define SCU_2500_COPRO_CACHE_EN 0x00000001 +#define SCU_2500_COPRO_SEG0_CACHE_EN 0x00000002 +#define SCU_2500_COPRO_SEG1_CACHE_EN 0x00000004 +#define SCU_2500_COPRO_SEG2_CACHE_EN 0x00000008 +#define SCU_2500_COPRO_SEG3_CACHE_EN 0x00000010 +#define SCU_2500_COPRO_SEG4_CACHE_EN 0x00000020 +#define SCU_2500_COPRO_SEG5_CACHE_EN 0x00000040 +#define SCU_2500_COPRO_SEG6_CACHE_EN 0x00000080 +#define SCU_2500_COPRO_SEG7_CACHE_EN 0x00000100 +#define SCU_2500_COPRO_SEG8_CACHE_EN 0x00000200 + +#define SCU_2400_COPRO_SEG0 0x104 +#define SCU_2400_COPRO_SEG2 0x108 +#define SCU_2400_COPRO_SEG4 0x10c +#define SCU_2400_COPRO_SEG6 0x110 +#define SCU_2400_COPRO_SEG8 0x114 +#define SCU_2400_COPRO_SEG_SWAP 0x80000000 +#define SCU_2400_COPRO_CACHE_CTL 0x118 +#define SCU_2400_COPRO_CACHE_EN 0x00000001 +#define SCU_2400_COPRO_SEG0_CACHE_EN 0x00000002 +#define SCU_2400_COPRO_SEG2_CACHE_EN 0x00000004 +#define SCU_2400_COPRO_SEG4_CACHE_EN 0x00000008 +#define SCU_2400_COPRO_SEG6_CACHE_EN 0x00000010 +#define SCU_2400_COPRO_SEG8_CACHE_EN 0x00000020 + +/* CVIC registers */ +#define CVIC_EN_REG 0x10 +#define CVIC_TRIG_REG 0x18 + +/* + * System register base address (needed for configuring the + * coldfire maps) + */ +#define SYSREG_BASE 0x1e600000 + +/* Amount of SRAM required */ +#define SRAM_SIZE 0x1000 + +#define LAST_ADDR_INVALID 0x1 + +struct fsi_master_acf { + struct fsi_master master; + struct device *dev; + struct regmap *scu; + struct mutex lock; /* mutex for command ordering */ + struct gpio_desc *gpio_clk; + struct gpio_desc *gpio_data; + struct gpio_desc *gpio_trans; /* Voltage translator */ + struct gpio_desc *gpio_enable; /* FSI enable */ + struct gpio_desc *gpio_mux; /* Mux control */ + uint16_t gpio_clk_vreg; + uint16_t gpio_clk_dreg; + uint16_t gpio_dat_vreg; + uint16_t gpio_dat_dreg; + uint16_t gpio_tra_vreg; + uint16_t gpio_tra_dreg; + uint8_t gpio_clk_bit; + uint8_t gpio_dat_bit; + uint8_t gpio_tra_bit; + uint32_t cf_mem_addr; + size_t cf_mem_size; + void __iomem *cf_mem; + void __iomem *cvic; + struct gen_pool *sram_pool; + void __iomem *sram; + bool is_ast2500; + bool external_mode; + bool trace_enabled; + uint32_t last_addr; + uint8_t t_send_delay; + uint8_t t_echo_delay; + uint32_t cvic_sw_irq; +}; +#define to_fsi_master_acf(m) container_of(m, struct fsi_master_acf, master) + +struct fsi_msg { + uint64_t msg; + uint8_t bits; +}; + +#define CREATE_TRACE_POINTS +#include + +static void msg_push_bits(struct fsi_msg *msg, uint64_t data, int bits) +{ + msg->msg <<= bits; + msg->msg |= data & ((1ull << bits) - 1); + msg->bits += bits; +} + +static void msg_push_crc(struct fsi_msg *msg) +{ + uint8_t crc; + int top; + + top = msg->bits & 0x3; + + /* start bit, and any non-aligned top bits */ + crc = crc4(0, 1 << top | msg->msg >> (msg->bits - top), top + 1); + + /* aligned bits */ + crc = crc4(crc, msg->msg, msg->bits - top); + + msg_push_bits(msg, crc, 4); +} + +static void msg_finish_cmd(struct fsi_msg *cmd) +{ + /* Left align message */ + cmd->msg <<= (64 - cmd->bits); +} + +static bool check_same_address(struct fsi_master_acf *master, int id, + uint32_t addr) +{ + /* this will also handle LAST_ADDR_INVALID */ + return master->last_addr == (((id & 0x3) << 21) | (addr & ~0x3)); +} + +static bool check_relative_address(struct fsi_master_acf *master, int id, + uint32_t addr, uint32_t *rel_addrp) +{ + uint32_t last_addr = master->last_addr; + int32_t rel_addr; + + if (last_addr == LAST_ADDR_INVALID) + return false; + + /* We may be in 23-bit addressing mode, which uses the id as the + * top two address bits. So, if we're referencing a different ID, + * use absolute addresses. + */ + if (((last_addr >> 21) & 0x3) != id) + return false; + + /* remove the top two bits from any 23-bit addressing */ + last_addr &= (1 << 21) - 1; + + /* We know that the addresses are limited to 21 bits, so this won't + * overflow the signed rel_addr */ + rel_addr = addr - last_addr; + if (rel_addr > 255 || rel_addr < -256) + return false; + + *rel_addrp = (uint32_t)rel_addr; + + return true; +} + +static void last_address_update(struct fsi_master_acf *master, + int id, bool valid, uint32_t addr) +{ + if (!valid) + master->last_addr = LAST_ADDR_INVALID; + else + master->last_addr = ((id & 0x3) << 21) | (addr & ~0x3); +} + +/* + * Encode an Absolute/Relative/Same Address command + */ +static void build_ar_command(struct fsi_master_acf *master, + struct fsi_msg *cmd, uint8_t id, + uint32_t addr, size_t size, + const void *data) +{ + int i, addr_bits, opcode_bits; + bool write = !!data; + uint8_t ds, opcode; + uint32_t rel_addr; + + cmd->bits = 0; + cmd->msg = 0; + + /* we have 21 bits of address max */ + addr &= ((1 << 21) - 1); + + /* cmd opcodes are variable length - SAME_AR is only two bits */ + opcode_bits = 3; + + if (check_same_address(master, id, addr)) { + /* we still address the byte offset within the word */ + addr_bits = 2; + opcode_bits = 2; + opcode = FSI_CMD_SAME_AR; + trace_fsi_master_acf_cmd_same_addr(master); + + } else if (check_relative_address(master, id, addr, &rel_addr)) { + /* 8 bits plus sign */ + addr_bits = 9; + addr = rel_addr; + opcode = FSI_CMD_REL_AR; + trace_fsi_master_acf_cmd_rel_addr(master, rel_addr); + + } else { + addr_bits = 21; + opcode = FSI_CMD_ABS_AR; + trace_fsi_master_acf_cmd_abs_addr(master, addr); + } + + /* + * The read/write size is encoded in the lower bits of the address + * (as it must be naturally-aligned), and the following ds bit. + * + * size addr:1 addr:0 ds + * 1 x x 0 + * 2 x 0 1 + * 4 0 1 1 + * + */ + ds = size > 1 ? 1 : 0; + addr &= ~(size - 1); + if (size == 4) + addr |= 1; + + msg_push_bits(cmd, id, 2); + msg_push_bits(cmd, opcode, opcode_bits); + msg_push_bits(cmd, write ? 0 : 1, 1); + msg_push_bits(cmd, addr, addr_bits); + msg_push_bits(cmd, ds, 1); + for (i = 0; write && i < size; i++) + msg_push_bits(cmd, ((uint8_t *)data)[i], 8); + + msg_push_crc(cmd); + msg_finish_cmd(cmd); +} + +static void build_dpoll_command(struct fsi_msg *cmd, uint8_t slave_id) +{ + cmd->bits = 0; + cmd->msg = 0; + + msg_push_bits(cmd, slave_id, 2); + msg_push_bits(cmd, FSI_CMD_DPOLL, 3); + msg_push_crc(cmd); + msg_finish_cmd(cmd); +} + +static void build_epoll_command(struct fsi_msg *cmd, uint8_t slave_id) +{ + cmd->bits = 0; + cmd->msg = 0; + + msg_push_bits(cmd, slave_id, 2); + msg_push_bits(cmd, FSI_CMD_EPOLL, 3); + msg_push_crc(cmd); + msg_finish_cmd(cmd); +} + +static void build_term_command(struct fsi_msg *cmd, uint8_t slave_id) +{ + cmd->bits = 0; + cmd->msg = 0; + + msg_push_bits(cmd, slave_id, 2); + msg_push_bits(cmd, FSI_CMD_TERM, 6); + msg_push_crc(cmd); + msg_finish_cmd(cmd); +} + +static int do_copro_command(struct fsi_master_acf *master, uint32_t op) +{ + uint32_t timeout = 10000000; + uint8_t stat; + + trace_fsi_master_acf_copro_command(master, op); + + /* Send command */ + iowrite32be(op, master->sram + CMD_STAT_REG); + + /* Ring doorbell if any */ + if (master->cvic) + iowrite32(0x2, master->cvic + CVIC_TRIG_REG); + + /* Wait for status to indicate completion (or error) */ + do { + if (timeout-- == 0) { + dev_warn(master->dev, + "Timeout waiting for coprocessor completion\n"); + return -ETIMEDOUT; + } + stat = ioread8(master->sram + CMD_STAT_REG); + } while(stat < STAT_COMPLETE || stat == 0xff); + + if (stat == STAT_COMPLETE) + return 0; + switch(stat) { + case STAT_ERR_INVAL_CMD: + return -EINVAL; + case STAT_ERR_INVAL_IRQ: + return -EIO; + case STAT_ERR_MTOE: + return -ESHUTDOWN; + } + return -ENXIO; +} + +static int clock_zeros(struct fsi_master_acf *master, int count) +{ + while (count) { + int rc, lcnt = min(count, 255); + + rc = do_copro_command(master, + CMD_IDLE_CLOCKS | (lcnt << CMD_REG_CLEN_SHIFT)); + if (rc) + return rc; + count -= lcnt; + } + return 0; +} + +static int send_request(struct fsi_master_acf *master, struct fsi_msg *cmd, + unsigned int resp_bits) +{ + uint32_t op; + + trace_fsi_master_acf_send_request(master, cmd, resp_bits); + + /* Store message into SRAM */ + iowrite32be((cmd->msg >> 32), master->sram + CMD_DATA); + iowrite32be((cmd->msg & 0xffffffff), master->sram + CMD_DATA + 4); + + op = CMD_COMMAND; + op |= cmd->bits << CMD_REG_CLEN_SHIFT; + if (resp_bits) + op |= resp_bits << CMD_REG_RLEN_SHIFT; + + return do_copro_command(master, op); +} + +static int read_copro_response(struct fsi_master_acf *master, uint8_t size, + uint32_t *response, u8 *tag) +{ + uint8_t rtag = ioread8(master->sram + STAT_RTAG); + uint8_t rcrc = ioread8(master->sram + STAT_RCRC); + uint32_t rdata = 0; + uint32_t crc; + uint8_t ack; + + *tag = ack = rtag & 3; + + /* we have a whole message now; check CRC */ + crc = crc4(0, 1, 1); + crc = crc4(crc, rtag, 4); + if (ack == FSI_RESP_ACK && size) { + rdata = ioread32be(master->sram + RSP_DATA); + crc = crc4(crc, rdata, size); + if (response) + *response = rdata; + } + crc = crc4(crc, rcrc, 4); + + trace_fsi_master_acf_copro_response(master, rtag, rcrc, rdata, crc == 0); + + if (crc) { + /* + * Check if it's all 1's or all 0's, that probably means + * the host is off + */ + if ((rtag == 0xf && rcrc == 0xf) || (rtag == 0 && rcrc == 0)) + return -ENODEV; + dev_dbg(master->dev, "Bad response CRC !\n"); + return -EAGAIN; + } + return 0; +} + +static int send_term(struct fsi_master_acf *master, uint8_t slave) +{ + struct fsi_msg cmd; + uint8_t tag; + int rc; + + build_term_command(&cmd, slave); + + rc = send_request(master, &cmd, 0); + if (rc) { + dev_warn(master->dev, "Error %d sending term\n", rc); + return rc; + } + + rc = read_copro_response(master, 0, NULL, &tag); + if (rc < 0) { + dev_err(master->dev, + "TERM failed; lost communication with slave\n"); + return -EIO; + } else if (tag != FSI_RESP_ACK) { + dev_err(master->dev, "TERM failed; response %d\n", tag); + return -EIO; + } + return 0; +} + +static void dump_trace(struct fsi_master_acf *master) +{ + char trbuf[52]; + char *p; + int i; + + dev_dbg(master->dev, + "CMDSTAT:%08x RTAG=%02x RCRC=%02x RDATA=%02x #INT=%08x\n", + ioread32be(master->sram + CMD_STAT_REG), + ioread8(master->sram + STAT_RTAG), + ioread8(master->sram + STAT_RCRC), + ioread32be(master->sram + RSP_DATA), + ioread32be(master->sram + INT_CNT)); + + for (i = 0; i < 512; i++) { + uint8_t v; + if ((i % 16) == 0) + p = trbuf; + v = ioread8(master->sram + TRACEBUF + i); + p += sprintf(p, "%02x ", v); + if (((i % 16) == 15) || v == TR_END) + dev_dbg(master->dev, "%s\n", trbuf); + if (v == TR_END) + break; + } +} + +static int handle_response(struct fsi_master_acf *master, + uint8_t slave, uint8_t size, void *data) +{ + int busy_count = 0, rc; + int crc_err_retries = 0; + struct fsi_msg cmd; + uint32_t response; + uint8_t tag; +retry: + rc = read_copro_response(master, size, &response, &tag); + + /* Handle retries on CRC errors */ + if (rc == -EAGAIN) { + /* Too many retries ? */ + if (crc_err_retries++ > FSI_CRC_ERR_RETRIES) { + /* + * Pass it up as a -EIO otherwise upper level will retry + * the whole command which isn't what we want here. + */ + rc = -EIO; + goto bail; + } + trace_fsi_master_acf_crc_rsp_error(master, crc_err_retries); + if (master->trace_enabled) + dump_trace(master); + rc = clock_zeros(master, FSI_MASTER_EPOLL_CLOCKS); + if (rc) { + dev_warn(master->dev, + "Error %d clocking zeros for E_POLL\n", rc); + return rc; + } + build_epoll_command(&cmd, slave); + rc = send_request(master, &cmd, size); + if (rc) { + dev_warn(master->dev, "Error %d sending E_POLL\n", rc); + return -EIO; + } + goto retry; + } + if (rc) + return rc; + + switch (tag) { + case FSI_RESP_ACK: + if (size && data) { + if (size == 32) + *(__be32 *)data = cpu_to_be32(response); + else if (size == 16) + *(__be16 *)data = cpu_to_be16(response); + else + *(u8 *)data = response; + } + break; + case FSI_RESP_BUSY: + /* + * Its necessary to clock slave before issuing + * d-poll, not indicated in the hardware protocol + * spec. < 20 clocks causes slave to hang, 21 ok. + */ + dev_dbg(master->dev, "Busy, retrying...\n"); + if (master->trace_enabled) + dump_trace(master); + rc = clock_zeros(master, FSI_MASTER_DPOLL_CLOCKS); + if (rc) { + dev_warn(master->dev, + "Error %d clocking zeros for D_POLL\n", rc); + break; + } + if (busy_count++ < FSI_MASTER_MAX_BUSY) { + build_dpoll_command(&cmd, slave); + rc = send_request(master, &cmd, size); + if (rc) { + dev_warn(master->dev, "Error %d sending D_POLL\n", rc); + break; + } + goto retry; + } + dev_dbg(master->dev, + "ERR slave is stuck in busy state, issuing TERM\n"); + send_term(master, slave); + rc = -EIO; + break; + + case FSI_RESP_ERRA: + dev_dbg(master->dev, "ERRA received\n"); + if (master->trace_enabled) + dump_trace(master); + rc = -EIO; + break; + case FSI_RESP_ERRC: + dev_dbg(master->dev, "ERRC received\n"); + if (master->trace_enabled) + dump_trace(master); + rc = -EAGAIN; + break; + } + bail: + if (busy_count > 0) { + trace_fsi_master_acf_poll_response_busy(master, busy_count); + } + + return rc; +} + +static int fsi_master_acf_xfer(struct fsi_master_acf *master, uint8_t slave, + struct fsi_msg *cmd, size_t resp_len, void *resp) +{ + int rc = -EAGAIN, retries = 0; + + resp_len <<= 3; + while ((retries++) < FSI_CRC_ERR_RETRIES) { + rc = send_request(master, cmd, resp_len); + if (rc) { + if (rc != -ESHUTDOWN) + dev_warn(master->dev, "Error %d sending command\n", rc); + break; + } + rc = handle_response(master, slave, resp_len, resp); + if (rc != -EAGAIN) + break; + rc = -EIO; + dev_dbg(master->dev, "ECRC retry %d\n", retries); + + /* Pace it a bit before retry */ + msleep(1); + } + + return rc; +} + +static int fsi_master_acf_read(struct fsi_master *_master, int link, + uint8_t id, uint32_t addr, void *val, + size_t size) +{ + struct fsi_master_acf *master = to_fsi_master_acf(_master); + struct fsi_msg cmd; + int rc; + + if (link != 0) + return -ENODEV; + + mutex_lock(&master->lock); + dev_dbg(master->dev, "read id %d addr %x size %ud\n", id, addr, size); + build_ar_command(master, &cmd, id, addr, size, NULL); + rc = fsi_master_acf_xfer(master, id, &cmd, size, val); + last_address_update(master, id, rc == 0, addr); + if (rc) + dev_dbg(master->dev, "read id %d addr 0x%08x err: %d\n", + id, addr, rc); + mutex_unlock(&master->lock); + + return rc; +} + +static int fsi_master_acf_write(struct fsi_master *_master, int link, + uint8_t id, uint32_t addr, const void *val, + size_t size) +{ + struct fsi_master_acf *master = to_fsi_master_acf(_master); + struct fsi_msg cmd; + int rc; + + if (link != 0) + return -ENODEV; + + mutex_lock(&master->lock); + build_ar_command(master, &cmd, id, addr, size, val); + dev_dbg(master->dev, "write id %d addr %x size %ud raw_data: %08x\n", + id, addr, size, *(uint32_t *)val); + rc = fsi_master_acf_xfer(master, id, &cmd, 0, NULL); + last_address_update(master, id, rc == 0, addr); + if (rc) + dev_dbg(master->dev, "write id %d addr 0x%08x err: %d\n", + id, addr, rc); + mutex_unlock(&master->lock); + + return rc; +} + +static int fsi_master_acf_term(struct fsi_master *_master, + int link, uint8_t id) +{ + struct fsi_master_acf *master = to_fsi_master_acf(_master); + struct fsi_msg cmd; + int rc; + + if (link != 0) + return -ENODEV; + + mutex_lock(&master->lock); + build_term_command(&cmd, id); + dev_dbg(master->dev, "term id %d\n", id); + rc = fsi_master_acf_xfer(master, id, &cmd, 0, NULL); + last_address_update(master, id, false, 0); + mutex_unlock(&master->lock); + + return rc; +} + +static int fsi_master_acf_break(struct fsi_master *_master, int link) +{ + struct fsi_master_acf *master = to_fsi_master_acf(_master); + int rc; + + if (link != 0) + return -ENODEV; + + mutex_lock(&master->lock); + if (master->external_mode) { + mutex_unlock(&master->lock); + return -EBUSY; + } + dev_dbg(master->dev, "sending BREAK\n"); + rc = do_copro_command(master, CMD_BREAK); + last_address_update(master, 0, false, 0); + mutex_unlock(&master->lock); + + /* Wait for logic reset to take effect */ + udelay(200); + + return rc; +} + +static void reset_cf(struct fsi_master_acf *master) +{ + regmap_write(master->scu, SCU_COPRO_CTRL, SCU_COPRO_RESET); + usleep_range(20,20); + regmap_write(master->scu, SCU_COPRO_CTRL, 0); + usleep_range(20,20); +} + +static void start_cf(struct fsi_master_acf *master) +{ + regmap_write(master->scu, SCU_COPRO_CTRL, SCU_COPRO_CLK_EN); +} + +static void setup_ast2500_cf_maps(struct fsi_master_acf *master) +{ + /* + * Note about byteswap setting: the bus is wired backwards, + * so setting the byteswap bit actually makes the ColdFire + * work "normally" for a BE processor, ie, put the MSB in + * the lowest address byte. + * + * We thus need to set the bit for our main memory which + * contains our program code. We create two mappings for + * the register, one with each setting. + * + * Segments 2 and 3 has a "swapped" mapping (BE) + * and 6 and 7 have a non-swapped mapping (LE) which allows + * us to avoid byteswapping register accesses since the + * registers are all LE. + */ + + /* Setup segment 0 to our memory region */ + regmap_write(master->scu, SCU_2500_COPRO_SEG0, master->cf_mem_addr | + SCU_2500_COPRO_SEG_SWAP); + + /* Segments 2 and 3 to sysregs with byteswap (for SRAM) */ + regmap_write(master->scu, SCU_2500_COPRO_SEG2, SYSREG_BASE | + SCU_2500_COPRO_SEG_SWAP); + regmap_write(master->scu, SCU_2500_COPRO_SEG3, SYSREG_BASE | 0x100000 | + SCU_2500_COPRO_SEG_SWAP); + + /* And segment 6 and 7 to sysregs no byteswap */ + regmap_write(master->scu, SCU_2500_COPRO_SEG6, SYSREG_BASE); + regmap_write(master->scu, SCU_2500_COPRO_SEG7, SYSREG_BASE | 0x100000); + + /* Memory cachable, regs and SRAM not cachable */ + regmap_write(master->scu, SCU_2500_COPRO_CACHE_CTL, + SCU_2500_COPRO_SEG0_CACHE_EN | SCU_2500_COPRO_CACHE_EN); +} + +static void setup_ast2400_cf_maps(struct fsi_master_acf *master) +{ + /* Setup segment 0 to our memory region */ + regmap_write(master->scu, SCU_2400_COPRO_SEG0, master->cf_mem_addr | + SCU_2400_COPRO_SEG_SWAP); + + /* Segments 2 to sysregs with byteswap (for SRAM) */ + regmap_write(master->scu, SCU_2400_COPRO_SEG2, SYSREG_BASE | + SCU_2400_COPRO_SEG_SWAP); + + /* And segment 6 to sysregs no byteswap */ + regmap_write(master->scu, SCU_2400_COPRO_SEG6, SYSREG_BASE); + + /* Memory cachable, regs and SRAM not cachable */ + regmap_write(master->scu, SCU_2400_COPRO_CACHE_CTL, + SCU_2400_COPRO_SEG0_CACHE_EN | SCU_2400_COPRO_CACHE_EN); +} + +static void setup_common_fw_config(struct fsi_master_acf *master, + void __iomem *base) +{ + iowrite16be(master->gpio_clk_vreg, base + HDR_CLOCK_GPIO_VADDR); + iowrite16be(master->gpio_clk_dreg, base + HDR_CLOCK_GPIO_DADDR); + iowrite16be(master->gpio_dat_vreg, base + HDR_DATA_GPIO_VADDR); + iowrite16be(master->gpio_dat_dreg, base + HDR_DATA_GPIO_DADDR); + iowrite16be(master->gpio_tra_vreg, base + HDR_TRANS_GPIO_VADDR); + iowrite16be(master->gpio_tra_dreg, base + HDR_TRANS_GPIO_DADDR); + iowrite8(master->gpio_clk_bit, base + HDR_CLOCK_GPIO_BIT); + iowrite8(master->gpio_dat_bit, base + HDR_DATA_GPIO_BIT); + iowrite8(master->gpio_tra_bit, base + HDR_TRANS_GPIO_BIT); +} + +static void setup_ast2500_fw_config(struct fsi_master_acf *master) +{ + void __iomem *base = master->cf_mem + HDR_OFFSET; + + setup_common_fw_config(master, base); + iowrite32be(FW_CONTROL_USE_STOP, base + HDR_FW_CONTROL); +} + +static void setup_ast2400_fw_config(struct fsi_master_acf *master) +{ + void __iomem *base = master->cf_mem + HDR_OFFSET; + + setup_common_fw_config(master, base); + iowrite32be(FW_CONTROL_CONT_CLOCK|FW_CONTROL_DUMMY_RD, base + HDR_FW_CONTROL); +} + +static int setup_gpios_for_copro(struct fsi_master_acf *master) +{ + + int rc; + + /* This aren't under ColdFire control, just set them up appropriately */ + gpiod_direction_output(master->gpio_mux, 1); + gpiod_direction_output(master->gpio_enable, 1); + + /* Those are under ColdFire control, let it configure them */ + rc = aspeed_gpio_copro_grab_gpio(master->gpio_clk, &master->gpio_clk_vreg, + &master->gpio_clk_dreg, &master->gpio_clk_bit); + if (rc) { + dev_err(master->dev, "failed to assign clock gpio to coprocessor\n"); + return rc; + } + rc = aspeed_gpio_copro_grab_gpio(master->gpio_data, &master->gpio_dat_vreg, + &master->gpio_dat_dreg, &master->gpio_dat_bit); + if (rc) { + dev_err(master->dev, "failed to assign data gpio to coprocessor\n"); + aspeed_gpio_copro_release_gpio(master->gpio_clk); + return rc; + } + rc = aspeed_gpio_copro_grab_gpio(master->gpio_trans, &master->gpio_tra_vreg, + &master->gpio_tra_dreg, &master->gpio_tra_bit); + if (rc) { + dev_err(master->dev, "failed to assign trans gpio to coprocessor\n"); + aspeed_gpio_copro_release_gpio(master->gpio_clk); + aspeed_gpio_copro_release_gpio(master->gpio_data); + return rc; + } + return 0; +} + +static void release_copro_gpios(struct fsi_master_acf *master) +{ + aspeed_gpio_copro_release_gpio(master->gpio_clk); + aspeed_gpio_copro_release_gpio(master->gpio_data); + aspeed_gpio_copro_release_gpio(master->gpio_trans); +} + +static int load_copro_firmware(struct fsi_master_acf *master) +{ + const struct firmware *fw; + uint16_t sig = 0, wanted_sig; + const u8 *data; + size_t size = 0; + int rc; + + /* Get the binary */ + rc = request_firmware(&fw, FW_FILE_NAME, master->dev); + if (rc) { + dev_err( + master->dev, "Error %d to load firwmare '%s' !\n", + rc, FW_FILE_NAME); + return rc; + } + + /* Which image do we want ? (shared vs. split clock/data GPIOs) */ + if (master->gpio_clk_vreg == master->gpio_dat_vreg) + wanted_sig = SYS_SIG_SHARED; + else + wanted_sig = SYS_SIG_SPLIT; + dev_dbg(master->dev, "Looking for image sig %04x\n", wanted_sig); + + /* Try to find it */ + for (data = fw->data; data < (fw->data + fw->size);) { + sig = be16_to_cpup((__be16 *)(data + HDR_OFFSET + HDR_SYS_SIG)); + size = be32_to_cpup((__be32 *)(data + HDR_OFFSET + HDR_FW_SIZE)); + if (sig == wanted_sig) + break; + data += size; + } + if (sig != wanted_sig) { + dev_err(master->dev, "Failed to locate image sig %04x in FW blob\n", + wanted_sig); + return -ENODEV; + } + if (size > master->cf_mem_size) { + dev_err(master->dev, "FW size (%zd) bigger than memory reserve (%zd)\n", + fw->size, master->cf_mem_size); + rc = -ENOMEM; + } else { + memcpy_toio(master->cf_mem, data, size); + } + release_firmware(fw); + + return rc; +} + +static int check_firmware_image(struct fsi_master_acf *master) +{ + uint32_t fw_vers, fw_api, fw_options; + + fw_vers = ioread16be(master->cf_mem + HDR_OFFSET + HDR_FW_VERS); + fw_api = ioread16be(master->cf_mem + HDR_OFFSET + HDR_API_VERS); + fw_options = ioread32be(master->cf_mem + HDR_OFFSET + HDR_FW_OPTIONS); + master->trace_enabled = !!(fw_options & FW_OPTION_TRACE_EN); + + /* Check version and signature */ + dev_info(master->dev, "ColdFire initialized, firmware v%d API v%d.%d (trace %s)\n", + fw_vers, fw_api >> 8, fw_api & 0xff, + master->trace_enabled ? "enabled" : "disabled"); + + if ((fw_api >> 8) != API_VERSION_MAJ) { + dev_err(master->dev, "Unsupported coprocessor API version !\n"); + return -ENODEV; + } + + return 0; +} + +static int copro_enable_sw_irq(struct fsi_master_acf *master) +{ + int timeout; + uint32_t val; + + /* + * Enable coprocessor interrupt input. I've had problems getting the + * value to stick, so try in a loop + */ + for (timeout = 0; timeout < 10; timeout++) { + iowrite32(0x2, master->cvic + CVIC_EN_REG); + val = ioread32(master->cvic + CVIC_EN_REG); + if (val & 2) + break; + msleep(1); + } + if (!(val & 2)) { + dev_err(master->dev, "Failed to enable coprocessor interrupt !\n"); + return -ENODEV; + } + return 0; +} + +static int fsi_master_acf_setup(struct fsi_master_acf *master) +{ + int timeout, rc; + uint32_t val; + + /* Make sure the ColdFire is stopped */ + reset_cf(master); + + /* + * Clear SRAM. This needs to happen before we setup the GPIOs + * as we might start trying to arbitrate as soon as that happens. + */ + memset_io(master->sram, 0, SRAM_SIZE); + + /* Configure GPIOs */ + rc = setup_gpios_for_copro(master); + if (rc) + return rc; + + /* Load the firmware into the reserved memory */ + rc = load_copro_firmware(master); + if (rc) + return rc; + + /* Read signature and check versions */ + rc = check_firmware_image(master); + if (rc) + return rc; + + /* Setup coldfire memory map */ + if (master->is_ast2500) { + setup_ast2500_cf_maps(master); + setup_ast2500_fw_config(master); + } else { + setup_ast2400_cf_maps(master); + setup_ast2400_fw_config(master); + } + + /* Start the ColdFire */ + start_cf(master); + + /* Wait for status register to indicate command completion + * which signals the initialization is complete + */ + for (timeout = 0; timeout < 10; timeout++) { + val = ioread8(master->sram + CF_STARTED); + if (val) + break; + msleep(1); + } + if (!val) { + dev_err(master->dev, "Coprocessor startup timeout !\n"); + rc = -ENODEV; + goto err; + } + + /* Configure echo & send delay */ + iowrite8(master->t_send_delay, master->sram + SEND_DLY_REG); + iowrite8(master->t_echo_delay, master->sram + ECHO_DLY_REG); + + /* Enable SW interrupt to copro if any */ + if (master->cvic) { + rc = copro_enable_sw_irq(master); + if (rc) + goto err; + } + return 0; + err: + /* An error occurred, don't leave the coprocessor running */ + reset_cf(master); + + /* Release the GPIOs */ + release_copro_gpios(master); + + return rc; +} + + +static void fsi_master_acf_terminate(struct fsi_master_acf *master) +{ + unsigned long flags; + + /* + * A GPIO arbitration requestion could come in while this is + * happening. To avoid problems, we disable interrupts so it + * cannot preempt us on this CPU + */ + + local_irq_save(flags); + + /* Stop the coprocessor */ + reset_cf(master); + + /* We mark the copro not-started */ + iowrite32(0, master->sram + CF_STARTED); + + /* We mark the ARB register as having given up arbitration to + * deal with a potential race with the arbitration request + */ + iowrite8(ARB_ARM_ACK, master->sram + ARB_REG); + + local_irq_restore(flags); + + /* Return the GPIOs to the ARM */ + release_copro_gpios(master); +} + +static void fsi_master_acf_setup_external(struct fsi_master_acf *master) +{ + /* Setup GPIOs for external FSI master (FSP box) */ + gpiod_direction_output(master->gpio_mux, 0); + gpiod_direction_output(master->gpio_trans, 0); + gpiod_direction_output(master->gpio_enable, 1); + gpiod_direction_input(master->gpio_clk); + gpiod_direction_input(master->gpio_data); +} + +static int fsi_master_acf_link_enable(struct fsi_master *_master, int link) +{ + struct fsi_master_acf *master = to_fsi_master_acf(_master); + int rc = -EBUSY; + + if (link != 0) + return -ENODEV; + + mutex_lock(&master->lock); + if (!master->external_mode) { + gpiod_set_value(master->gpio_enable, 1); + rc = 0; + } + mutex_unlock(&master->lock); + + return rc; +} + +static int fsi_master_acf_link_config(struct fsi_master *_master, int link, + u8 t_send_delay, u8 t_echo_delay) +{ + struct fsi_master_acf *master = to_fsi_master_acf(_master); + + if (link != 0) + return -ENODEV; + + mutex_lock(&master->lock); + master->t_send_delay = t_send_delay; + master->t_echo_delay = t_echo_delay; + dev_dbg(master->dev, "Changing delays: send=%d echo=%d\n", + t_send_delay, t_echo_delay); + iowrite8(master->t_send_delay, master->sram + SEND_DLY_REG); + iowrite8(master->t_echo_delay, master->sram + ECHO_DLY_REG); + mutex_unlock(&master->lock); + + return 0; +} + +static ssize_t external_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fsi_master_acf *master = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE - 1, "%u\n", + master->external_mode ? 1 : 0); +} + +static ssize_t external_mode_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct fsi_master_acf *master = dev_get_drvdata(dev); + unsigned long val; + bool external_mode; + int err; + + err = kstrtoul(buf, 0, &val); + if (err) + return err; + + external_mode = !!val; + + mutex_lock(&master->lock); + + if (external_mode == master->external_mode) { + mutex_unlock(&master->lock); + return count; + } + + master->external_mode = external_mode; + if (master->external_mode) { + fsi_master_acf_terminate(master); + fsi_master_acf_setup_external(master); + } else + fsi_master_acf_setup(master); + + mutex_unlock(&master->lock); + + fsi_master_rescan(&master->master); + + return count; +} + +static DEVICE_ATTR(external_mode, 0664, + external_mode_show, external_mode_store); + +static int fsi_master_acf_gpio_request(void *data) +{ + struct fsi_master_acf *master = data; + int timeout; + u8 val; + + /* Note: This doesn't require holding out mutex */ + + /* Write reqest */ + iowrite8(ARB_ARM_REQ, master->sram + ARB_REG); + + /* + * There is a race (which does happen at boot time) when we get an + * arbitration request as we are either about to or just starting + * the coprocessor. + * + * To handle it, we first check if we are running. If not yet we + * check whether the copro is started in the SCU. + * + * If it's not started, we can basically just assume we have arbitration + * and return. Otherwise, we wait normally expecting for the arbitration + * to eventually complete. + */ + if (ioread32(master->sram + CF_STARTED) == 0) { + unsigned int reg = 0; + + regmap_read(master->scu, SCU_COPRO_CTRL, ®); + if (!(reg & SCU_COPRO_CLK_EN)) + return 0; + } + + /* Ring doorbell if any */ + if (master->cvic) + iowrite32(0x2, master->cvic + CVIC_TRIG_REG); + + for (timeout = 0; timeout < 10000; timeout++) { + val = ioread8(master->sram + ARB_REG); + if (val != ARB_ARM_REQ) + break; + udelay(1); + } + + /* If it failed, override anyway */ + if (val != ARB_ARM_ACK) + dev_warn(master->dev, "GPIO request arbitration timeout\n"); + + return 0; +} + +static int fsi_master_acf_gpio_release(void *data) +{ + struct fsi_master_acf *master = data; + + /* Write release */ + iowrite8(0, master->sram + ARB_REG); + + /* Ring doorbell if any */ + if (master->cvic) + iowrite32(0x2, master->cvic + CVIC_TRIG_REG); + + return 0; +} + +static void fsi_master_acf_release(struct device *dev) +{ + struct fsi_master_acf *master = to_fsi_master_acf(dev_to_fsi_master(dev)); + + /* Cleanup, stop coprocessor */ + mutex_lock(&master->lock); + fsi_master_acf_terminate(master); + aspeed_gpio_copro_set_ops(NULL, NULL); + mutex_unlock(&master->lock); + + /* Free resources */ + gen_pool_free(master->sram_pool, (unsigned long)master->sram, SRAM_SIZE); + of_node_put(dev_of_node(master->dev)); + + kfree(master); +} + +static const struct aspeed_gpio_copro_ops fsi_master_acf_gpio_ops = { + .request_access = fsi_master_acf_gpio_request, + .release_access = fsi_master_acf_gpio_release, +}; + +static int fsi_master_acf_probe(struct platform_device *pdev) +{ + struct device_node *np, *mnode = dev_of_node(&pdev->dev); + struct genpool_data_fixed gpdf; + struct fsi_master_acf *master; + struct gpio_desc *gpio; + struct resource res; + uint32_t cf_mem_align; + int rc; + + master = kzalloc(sizeof(*master), GFP_KERNEL); + if (!master) + return -ENOMEM; + + master->dev = &pdev->dev; + master->master.dev.parent = master->dev; + master->last_addr = LAST_ADDR_INVALID; + + /* AST2400 vs. AST2500 */ + master->is_ast2500 = of_device_is_compatible(mnode, "aspeed,ast2500-cf-fsi-master"); + + /* Grab the SCU, we'll need to access it to configure the coprocessor */ + if (master->is_ast2500) + master->scu = syscon_regmap_lookup_by_compatible("aspeed,ast2500-scu"); + else + master->scu = syscon_regmap_lookup_by_compatible("aspeed,ast2400-scu"); + if (IS_ERR(master->scu)) { + dev_err(&pdev->dev, "failed to find SCU regmap\n"); + rc = PTR_ERR(master->scu); + goto err_free; + } + + /* Grab all the GPIOs we need */ + gpio = devm_gpiod_get(&pdev->dev, "clock", 0); + if (IS_ERR(gpio)) { + dev_err(&pdev->dev, "failed to get clock gpio\n"); + rc = PTR_ERR(gpio); + goto err_free; + } + master->gpio_clk = gpio; + + gpio = devm_gpiod_get(&pdev->dev, "data", 0); + if (IS_ERR(gpio)) { + dev_err(&pdev->dev, "failed to get data gpio\n"); + rc = PTR_ERR(gpio); + goto err_free; + } + master->gpio_data = gpio; + + /* Optional GPIOs */ + gpio = devm_gpiod_get_optional(&pdev->dev, "trans", 0); + if (IS_ERR(gpio)) { + dev_err(&pdev->dev, "failed to get trans gpio\n"); + rc = PTR_ERR(gpio); + goto err_free; + } + master->gpio_trans = gpio; + + gpio = devm_gpiod_get_optional(&pdev->dev, "enable", 0); + if (IS_ERR(gpio)) { + dev_err(&pdev->dev, "failed to get enable gpio\n"); + rc = PTR_ERR(gpio); + goto err_free; + } + master->gpio_enable = gpio; + + gpio = devm_gpiod_get_optional(&pdev->dev, "mux", 0); + if (IS_ERR(gpio)) { + dev_err(&pdev->dev, "failed to get mux gpio\n"); + rc = PTR_ERR(gpio); + goto err_free; + } + master->gpio_mux = gpio; + + /* Grab the reserved memory region (use DMA API instead ?) */ + np = of_parse_phandle(mnode, "memory-region", 0); + if (!np) { + dev_err(&pdev->dev, "Didn't find reserved memory\n"); + rc = -EINVAL; + goto err_free; + } + rc = of_address_to_resource(np, 0, &res); + of_node_put(np); + if (rc) { + dev_err(&pdev->dev, "Couldn't address to resource for reserved memory\n"); + rc = -ENOMEM; + goto err_free; + } + master->cf_mem_size = resource_size(&res); + master->cf_mem_addr = (uint32_t)res.start; + cf_mem_align = master->is_ast2500 ? 0x00100000 : 0x00200000; + if (master->cf_mem_addr & (cf_mem_align - 1)) { + dev_err(&pdev->dev, "Reserved memory has insufficient alignment\n"); + rc = -ENOMEM; + goto err_free; + } + master->cf_mem = devm_ioremap_resource(&pdev->dev, &res); + if (IS_ERR(master->cf_mem)) { + rc = PTR_ERR(master->cf_mem); + dev_err(&pdev->dev, "Error %d mapping coldfire memory\n", rc); + goto err_free; + } + dev_dbg(&pdev->dev, "DRAM allocation @%x\n", master->cf_mem_addr); + + /* AST2500 has a SW interrupt to the coprocessor */ + if (master->is_ast2500) { + /* Grab the CVIC (ColdFire interrupts controller) */ + np = of_parse_phandle(mnode, "aspeed,cvic", 0); + if (!np) { + dev_err(&pdev->dev, "Didn't find CVIC\n"); + rc = -EINVAL; + goto err_free; + } + master->cvic = devm_of_iomap(&pdev->dev, np, 0, NULL); + if (IS_ERR(master->cvic)) { + rc = PTR_ERR(master->cvic); + dev_err(&pdev->dev, "Error %d mapping CVIC\n", rc); + goto err_free; + } + rc = of_property_read_u32(np, "copro-sw-interrupts", + &master->cvic_sw_irq); + if (rc) { + dev_err(&pdev->dev, "Can't find coprocessor SW interrupt\n"); + goto err_free; + } + } + + /* Grab the SRAM */ + master->sram_pool = of_gen_pool_get(dev_of_node(&pdev->dev), "aspeed,sram", 0); + if (!master->sram_pool) { + rc = -ENODEV; + dev_err(&pdev->dev, "Can't find sram pool\n"); + goto err_free; + } + + /* Current microcode only deals with fixed location in SRAM */ + gpdf.offset = 0; + master->sram = (void __iomem *)gen_pool_alloc_algo(master->sram_pool, SRAM_SIZE, + gen_pool_fixed_alloc, &gpdf); + if (!master->sram) { + rc = -ENOMEM; + dev_err(&pdev->dev, "Failed to allocate sram from pool\n"); + goto err_free; + } + dev_dbg(&pdev->dev, "SRAM allocation @%lx\n", + (unsigned long)gen_pool_virt_to_phys(master->sram_pool, + (unsigned long)master->sram)); + + /* + * Hookup with the GPIO driver for arbitration of GPIO banks + * ownership. + */ + aspeed_gpio_copro_set_ops(&fsi_master_acf_gpio_ops, master); + + /* Default FSI command delays */ + master->t_send_delay = FSI_SEND_DELAY_CLOCKS; + master->t_echo_delay = FSI_ECHO_DELAY_CLOCKS; + master->master.n_links = 1; + if (master->is_ast2500) + master->master.flags = FSI_MASTER_FLAG_SWCLOCK; + master->master.read = fsi_master_acf_read; + master->master.write = fsi_master_acf_write; + master->master.term = fsi_master_acf_term; + master->master.send_break = fsi_master_acf_break; + master->master.link_enable = fsi_master_acf_link_enable; + master->master.link_config = fsi_master_acf_link_config; + master->master.dev.of_node = of_node_get(dev_of_node(master->dev)); + master->master.dev.release = fsi_master_acf_release; + platform_set_drvdata(pdev, master); + mutex_init(&master->lock); + + mutex_lock(&master->lock); + rc = fsi_master_acf_setup(master); + mutex_unlock(&master->lock); + if (rc) + goto release_of_dev; + + rc = device_create_file(&pdev->dev, &dev_attr_external_mode); + if (rc) + goto stop_copro; + + rc = fsi_master_register(&master->master); + if (!rc) + return 0; + + device_remove_file(master->dev, &dev_attr_external_mode); + put_device(&master->master.dev); + return rc; + + stop_copro: + fsi_master_acf_terminate(master); + release_of_dev: + aspeed_gpio_copro_set_ops(NULL, NULL); + gen_pool_free(master->sram_pool, (unsigned long)master->sram, SRAM_SIZE); + of_node_put(dev_of_node(master->dev)); + err_free: + kfree(master); + return rc; +} + + +static int fsi_master_acf_remove(struct platform_device *pdev) +{ + struct fsi_master_acf *master = platform_get_drvdata(pdev); + + device_remove_file(master->dev, &dev_attr_external_mode); + + fsi_master_unregister(&master->master); + + return 0; +} + +static const struct of_device_id fsi_master_acf_match[] = { + { .compatible = "aspeed,ast2400-cf-fsi-master" }, + { .compatible = "aspeed,ast2500-cf-fsi-master" }, + { }, +}; + +static struct platform_driver fsi_master_acf = { + .driver = { + .name = "fsi-master-acf", + .of_match_table = fsi_master_acf_match, + }, + .probe = fsi_master_acf_probe, + .remove = fsi_master_acf_remove, +}; + +module_platform_driver(fsi_master_acf); +MODULE_LICENSE("GPL"); diff --git a/include/trace/events/fsi_master_ast_cf.h b/include/trace/events/fsi_master_ast_cf.h new file mode 100644 index 000000000000..a0fdfa58622a --- /dev/null +++ b/include/trace/events/fsi_master_ast_cf.h @@ -0,0 +1,150 @@ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM fsi_master_ast_cf + +#if !defined(_TRACE_FSI_MASTER_ACF_H) || defined(TRACE_HEADER_MULTI_READ) +#define _TRACE_FSI_MASTER_ACF_H + +#include + +TRACE_EVENT(fsi_master_acf_copro_command, + TP_PROTO(const struct fsi_master_acf *master, uint32_t op), + TP_ARGS(master, op), + TP_STRUCT__entry( + __field(int, master_idx) + __field(uint32_t, op) + ), + TP_fast_assign( + __entry->master_idx = master->master.idx; + __entry->op = op; + ), + TP_printk("fsi-acf%d command %08x", + __entry->master_idx, __entry->op + ) +); + +TRACE_EVENT(fsi_master_acf_send_request, + TP_PROTO(const struct fsi_master_acf *master, const struct fsi_msg *cmd, u8 rbits), + TP_ARGS(master, cmd, rbits), + TP_STRUCT__entry( + __field(int, master_idx) + __field(uint64_t, msg) + __field(u8, bits) + __field(u8, rbits) + ), + TP_fast_assign( + __entry->master_idx = master->master.idx; + __entry->msg = cmd->msg; + __entry->bits = cmd->bits; + __entry->rbits = rbits; + ), + TP_printk("fsi-acf%d cmd: %016llx/%d/%d", + __entry->master_idx, (unsigned long long)__entry->msg, + __entry->bits, __entry->rbits + ) +); + +TRACE_EVENT(fsi_master_acf_copro_response, + TP_PROTO(const struct fsi_master_acf *master, u8 rtag, u8 rcrc, __be32 rdata, bool crc_ok), + TP_ARGS(master, rtag, rcrc, rdata, crc_ok), + TP_STRUCT__entry( + __field(int, master_idx) + __field(u8, rtag) + __field(u8, rcrc) + __field(u32, rdata) + __field(bool, crc_ok) + ), + TP_fast_assign( + __entry->master_idx = master->master.idx; + __entry->rtag = rtag; + __entry->rcrc = rcrc; + __entry->rdata = be32_to_cpu(rdata); + __entry->crc_ok = crc_ok; + ), + TP_printk("fsi-acf%d rsp: tag=%04x crc=%04x data=%08x %c\n", + __entry->master_idx, __entry->rtag, __entry->rcrc, + __entry->rdata, __entry->crc_ok ? ' ' : '!' + ) +); + +TRACE_EVENT(fsi_master_acf_crc_rsp_error, + TP_PROTO(const struct fsi_master_acf *master, int retries), + TP_ARGS(master, retries), + TP_STRUCT__entry( + __field(int, master_idx) + __field(int, retries) + ), + TP_fast_assign( + __entry->master_idx = master->master.idx; + __entry->retries = retries; + ), + TP_printk("fsi-acf%d CRC error in response retry %d", + __entry->master_idx, __entry->retries + ) +); + +TRACE_EVENT(fsi_master_acf_poll_response_busy, + TP_PROTO(const struct fsi_master_acf *master, int busy_count), + TP_ARGS(master, busy_count), + TP_STRUCT__entry( + __field(int, master_idx) + __field(int, busy_count) + ), + TP_fast_assign( + __entry->master_idx = master->master.idx; + __entry->busy_count = busy_count; + ), + TP_printk("fsi-acf%d: device reported busy %d times", + __entry->master_idx, __entry->busy_count + ) +); + +TRACE_EVENT(fsi_master_acf_cmd_abs_addr, + TP_PROTO(const struct fsi_master_acf *master, u32 addr), + TP_ARGS(master, addr), + TP_STRUCT__entry( + __field(int, master_idx) + __field(u32, addr) + ), + TP_fast_assign( + __entry->master_idx = master->master.idx; + __entry->addr = addr; + ), + TP_printk("fsi-acf%d: Sending ABS_ADR %06x", + __entry->master_idx, __entry->addr + ) +); + +TRACE_EVENT(fsi_master_acf_cmd_rel_addr, + TP_PROTO(const struct fsi_master_acf *master, u32 rel_addr), + TP_ARGS(master, rel_addr), + TP_STRUCT__entry( + __field(int, master_idx) + __field(u32, rel_addr) + ), + TP_fast_assign( + __entry->master_idx = master->master.idx; + __entry->rel_addr = rel_addr; + ), + TP_printk("fsi-acf%d: Sending REL_ADR %03x", + __entry->master_idx, __entry->rel_addr + ) +); + +TRACE_EVENT(fsi_master_acf_cmd_same_addr, + TP_PROTO(const struct fsi_master_acf *master), + TP_ARGS(master), + TP_STRUCT__entry( + __field(int, master_idx) + ), + TP_fast_assign( + __entry->master_idx = master->master.idx; + ), + TP_printk("fsi-acf%d: Sending SAME_ADR", + __entry->master_idx + ) +); + +#endif /* _TRACE_FSI_MASTER_ACF_H */ + +#include -- cgit v1.2.3 From 4fcdc2d1b0c615331eb68b50da8e499223fac724 Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Mon, 23 Jul 2018 16:27:21 +1000 Subject: fsi: sbefifo: Fix inconsistent use of ffdc mutex Some of the exit path missed the unlock. Move the mutex to an outer function to avoid the problem completely Signed-off-by: Benjamin Herrenschmidt --- drivers/fsi/fsi-sbefifo.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/fsi/fsi-sbefifo.c b/drivers/fsi/fsi-sbefifo.c index 6b31cc24fb0d..33a5d9a43a07 100644 --- a/drivers/fsi/fsi-sbefifo.c +++ b/drivers/fsi/fsi-sbefifo.c @@ -136,16 +136,14 @@ struct sbefifo_user { static DEFINE_IDA(sbefifo_ida); static DEFINE_MUTEX(sbefifo_ffdc_mutex); - -static void sbefifo_dump_ffdc(struct device *dev, const __be32 *ffdc, - size_t ffdc_sz, bool internal) +static void __sbefifo_dump_ffdc(struct device *dev, const __be32 *ffdc, + size_t ffdc_sz, bool internal) { int pack = 0; #define FFDC_LSIZE 60 static char ffdc_line[FFDC_LSIZE]; char *p = ffdc_line; - mutex_lock(&sbefifo_ffdc_mutex); while (ffdc_sz) { u32 w0, w1, w2, i; if (ffdc_sz < 3) { @@ -194,6 +192,13 @@ static void sbefifo_dump_ffdc(struct device *dev, const __be32 *ffdc, } dev_warn(dev, "+-------------------------------------------+\n"); } +} + +static void sbefifo_dump_ffdc(struct device *dev, const __be32 *ffdc, + size_t ffdc_sz, bool internal) +{ + mutex_lock(&sbefifo_ffdc_mutex); + __sbefifo_dump_ffdc(dev, ffdc, ffdc_sz, internal); mutex_unlock(&sbefifo_ffdc_mutex); } -- cgit v1.2.3 From e13c542d5227481836e91f7b3a025e2c9092bc04 Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Wed, 20 Jun 2018 15:15:00 +1000 Subject: dt-bindings: fsi: Add optional chip-id to CFAMs This represents a physical chip in the system and allows a stable numbering scheme to be passed to udev for userspace to recognize which chip is which. Signed-off-by: Benjamin Herrenschmidt Acked-by: Rob Herring --- Documentation/devicetree/bindings/fsi/fsi.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Documentation/devicetree/bindings/fsi/fsi.txt b/Documentation/devicetree/bindings/fsi/fsi.txt index ab516c673a4b..afb4eccab131 100644 --- a/Documentation/devicetree/bindings/fsi/fsi.txt +++ b/Documentation/devicetree/bindings/fsi/fsi.txt @@ -83,6 +83,10 @@ addresses and sizes in the slave address space: #address-cells = <1>; #size-cells = <1>; +Optionally, a slave can provide a global unique chip ID which is used to +identify the physical location of the chip in a system specific way + + chip-id = <0>; FSI engines (devices) --------------------- @@ -125,6 +129,7 @@ device tree if no extra platform information is required. reg = <0 0>; #address-cells = <1>; #size-cells = <1>; + chip-id = <0>; /* FSI engine at 0xc00, using a single page. In this example, * it's an I2C master controller, so subnodes describe the -- cgit v1.2.3 From 0a213777d1dd879092225a7aa847b6e9b3a1c267 Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Wed, 20 Jun 2018 15:16:31 +1000 Subject: fsi: Add support for device-tree provided chip IDs They get retrieved from the device-tree and exposed as an attribute in sysfs Signed-off-by: Benjamin Herrenschmidt --- drivers/fsi/fsi-core.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/drivers/fsi/fsi-core.c b/drivers/fsi/fsi-core.c index e9f8813b75e6..eab6c5c4990e 100644 --- a/drivers/fsi/fsi-core.c +++ b/drivers/fsi/fsi-core.c @@ -80,6 +80,7 @@ struct fsi_slave { struct fsi_master *master; int id; int link; + int chip_id; uint32_t size; /* size of slave address space */ u8 t_send_delay; u8 t_echo_delay; @@ -717,6 +718,17 @@ static ssize_t slave_send_echo_store(struct device *dev, static DEVICE_ATTR(send_echo_delays, 0600, slave_send_echo_show, slave_send_echo_store); +static ssize_t chip_id_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct fsi_slave *slave = to_fsi_slave(dev); + + return sprintf(buf, "%d\n", slave->chip_id); +} + +static DEVICE_ATTR_RO(chip_id); + static int fsi_slave_init(struct fsi_master *master, int link, uint8_t id) { uint32_t chip_id; @@ -780,6 +792,14 @@ static int fsi_slave_init(struct fsi_master *master, int link, uint8_t id) slave->t_send_delay = 16; slave->t_echo_delay = 16; + /* Get chip ID if any */ + slave->chip_id = -1; + if (slave->dev.of_node) { + uint32_t prop; + if (!of_property_read_u32(slave->dev.of_node, "chip-id", &prop)) + slave->chip_id = prop; + + } rc = fsi_slave_set_smode(slave); if (rc) { dev_warn(&master->dev, @@ -814,6 +834,10 @@ static int fsi_slave_init(struct fsi_master *master, int link, uint8_t id) if (rc) dev_warn(&slave->dev, "failed to create delay attr: %d\n", rc); + rc = device_create_file(&slave->dev, &dev_attr_chip_id); + if (rc) + dev_warn(&slave->dev, "failed to create chip id: %d\n", rc); + rc = fsi_slave_scan(slave); if (rc) dev_dbg(&master->dev, "failed during slave scan with: %d\n", -- cgit v1.2.3 From d0cff240b83babe72f5ba28044268b86aaa91d53 Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Tue, 24 Jul 2018 16:35:03 +1000 Subject: fsi: master-ast-cf: Fix build warnings on 64-bit platforms A couple of places forgot the 'z' qualifier for dev_dbg when printing a size_t Signed-off-by: Benjamin Herrenschmidt --- drivers/fsi/fsi-master-ast-cf.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/fsi/fsi-master-ast-cf.c b/drivers/fsi/fsi-master-ast-cf.c index 57afaae0b691..c4a35579bfdc 100644 --- a/drivers/fsi/fsi-master-ast-cf.c +++ b/drivers/fsi/fsi-master-ast-cf.c @@ -606,7 +606,7 @@ static int fsi_master_acf_read(struct fsi_master *_master, int link, return -ENODEV; mutex_lock(&master->lock); - dev_dbg(master->dev, "read id %d addr %x size %ud\n", id, addr, size); + dev_dbg(master->dev, "read id %d addr %x size %zd\n", id, addr, size); build_ar_command(master, &cmd, id, addr, size, NULL); rc = fsi_master_acf_xfer(master, id, &cmd, size, val); last_address_update(master, id, rc == 0, addr); @@ -631,7 +631,7 @@ static int fsi_master_acf_write(struct fsi_master *_master, int link, mutex_lock(&master->lock); build_ar_command(master, &cmd, id, addr, size, val); - dev_dbg(master->dev, "write id %d addr %x size %ud raw_data: %08x\n", + dev_dbg(master->dev, "write id %d addr %x size %zd raw_data: %08x\n", id, addr, size, *(uint32_t *)val); rc = fsi_master_acf_xfer(master, id, &cmd, 0, NULL); last_address_update(master, id, rc == 0, addr); -- cgit v1.2.3 From 9fc5f0f8ad28405145b30fd3b905e368063ee14c Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Mon, 23 Jul 2018 13:21:22 +0300 Subject: mei: cleanup slots to data conversions Cleanup conversions between slots and data. Define MEI_SLOT_SIZE instead of using 4 or sizeof(u32) across the source code. Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/client.c | 2 +- drivers/misc/mei/hw-me.c | 8 ++++---- drivers/misc/mei/hw-txe.c | 8 ++++---- drivers/misc/mei/mei_dev.h | 8 +++++--- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c index 5a673d09585f..79e200d71652 100644 --- a/drivers/misc/mei/client.c +++ b/drivers/misc/mei/client.c @@ -1597,7 +1597,7 @@ int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb, /* Split the message only if we can write the whole host buffer */ } else if ((u32)slots == dev->hbuf_depth) { msg_slots = slots; - len = (slots * sizeof(u32)) - sizeof(struct mei_msg_hdr); + len = mei_slots2data(slots) - sizeof(struct mei_msg_hdr); mei_hdr.length = len; mei_hdr.msg_complete = 0; } else { diff --git a/drivers/misc/mei/hw-me.c b/drivers/misc/mei/hw-me.c index 5bbea13ab171..016b7c956f18 100644 --- a/drivers/misc/mei/hw-me.c +++ b/drivers/misc/mei/hw-me.c @@ -511,7 +511,7 @@ static int mei_me_hbuf_empty_slots(struct mei_device *dev) */ static size_t mei_me_hbuf_max_len(const struct mei_device *dev) { - return dev->hbuf_depth * sizeof(u32) - sizeof(struct mei_msg_hdr); + return mei_slots2data(dev->hbuf_depth) - sizeof(struct mei_msg_hdr); } @@ -549,7 +549,7 @@ static int mei_me_hbuf_write(struct mei_device *dev, mei_me_hcbww_write(dev, *((u32 *) header)); - for (i = 0; i < length / 4; i++) + for (i = 0; i < length / MEI_SLOT_SIZE; i++) mei_me_hcbww_write(dev, reg_buf[i]); rem = length & 0x3; @@ -604,11 +604,11 @@ static int mei_me_count_full_read_slots(struct mei_device *dev) * Return: always 0 */ static int mei_me_read_slots(struct mei_device *dev, unsigned char *buffer, - unsigned long buffer_length) + unsigned long buffer_length) { u32 *reg_buf = (u32 *)buffer; - for (; buffer_length >= sizeof(u32); buffer_length -= sizeof(u32)) + for (; buffer_length >= MEI_SLOT_SIZE; buffer_length -= MEI_SLOT_SIZE) *reg_buf++ = mei_me_mecbrw_read(dev); if (buffer_length > 0) { diff --git a/drivers/misc/mei/hw-txe.c b/drivers/misc/mei/hw-txe.c index a5e551ffb2dd..0facd823634e 100644 --- a/drivers/misc/mei/hw-txe.c +++ b/drivers/misc/mei/hw-txe.c @@ -682,7 +682,7 @@ static void mei_txe_hw_config(struct mei_device *dev) struct mei_txe_hw *hw = to_txe_hw(dev); /* Doesn't change in runtime */ - dev->hbuf_depth = PAYLOAD_SIZE / 4; + dev->hbuf_depth = PAYLOAD_SIZE / MEI_SLOT_SIZE; hw->aliveness = mei_txe_aliveness_get(dev); hw->readiness = mei_txe_readiness_get(dev); @@ -766,7 +766,7 @@ static int mei_txe_write(struct mei_device *dev, * * @dev: the device structure * - * Return: the PAYLOAD_SIZE - 4 + * Return: the PAYLOAD_SIZE - header size */ static size_t mei_txe_hbuf_max_len(const struct mei_device *dev) { @@ -797,7 +797,7 @@ static int mei_txe_hbuf_empty_slots(struct mei_device *dev) static int mei_txe_count_full_read_slots(struct mei_device *dev) { /* read buffers has static size */ - return PAYLOAD_SIZE / 4; + return PAYLOAD_SIZE / MEI_SLOT_SIZE; } /** @@ -839,7 +839,7 @@ static int mei_txe_read(struct mei_device *dev, dev_dbg(dev->dev, "buffer-length = %lu buf[0]0x%08X\n", len, mei_txe_out_data_read(dev, 0)); - for (i = 0; i < len / 4; i++) { + for (i = 0; i < len / MEI_SLOT_SIZE; i++) { /* skip header: index starts from 1 */ reg = mei_txe_out_data_read(dev, i + 1); dev_dbg(dev->dev, "buf[%d] = 0x%08X\n", i, reg); diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index d522585b71b7..897126dca5d0 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -26,7 +26,9 @@ #include "hw.h" #include "hbm.h" -#define MEI_RD_MSG_BUF_SIZE (128 * sizeof(u32)) +#define MEI_SLOT_SIZE sizeof(u32) +#define MEI_RD_MSG_BUF_SIZE (128 * MEI_SLOT_SIZE) + /* * Number of Maximum MEI Clients @@ -540,7 +542,7 @@ static inline unsigned long mei_secs_to_jiffies(unsigned long sec) */ static inline u32 mei_data2slots(size_t length) { - return DIV_ROUND_UP(sizeof(struct mei_msg_hdr) + length, 4); + return DIV_ROUND_UP(sizeof(struct mei_msg_hdr) + length, MEI_SLOT_SIZE); } /** @@ -552,7 +554,7 @@ static inline u32 mei_data2slots(size_t length) */ static inline u32 mei_slots2data(int slots) { - return slots * 4; + return slots * MEI_SLOT_SIZE; } /* -- cgit v1.2.3 From 8c8d964ce90f16877b76c3f00b27165bf865af69 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Mon, 23 Jul 2018 13:21:23 +0300 Subject: mei: move hbuf_depth from the mei device to the hw modules The host buffer depth is hardware specific so it's better to handle it inside the me and txe hw modules. In me the depth is read from register in txe it's a constant number. The value is now retrieved via mei_hbuf_depth accessor, while it replaces mei_hbuf_max_len. Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/client.c | 53 +++++++++++++++++++++++++++------------------- drivers/misc/mei/hw-me.c | 19 ++++++++++------- drivers/misc/mei/hw-me.h | 2 ++ drivers/misc/mei/hw-txe.c | 22 +++++++++---------- drivers/misc/mei/mei_dev.h | 11 ++++------ 5 files changed, 58 insertions(+), 49 deletions(-) diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c index 79e200d71652..ca917b84ca5e 100644 --- a/drivers/misc/mei/client.c +++ b/drivers/misc/mei/client.c @@ -1556,8 +1556,8 @@ int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb, struct mei_msg_data *buf; struct mei_msg_hdr mei_hdr; size_t len; - u32 msg_slots; - int slots; + size_t hbuf_len; + int hbuf_slots; int rets; bool first_chunk; @@ -1579,29 +1579,30 @@ int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb, return 0; } - slots = mei_hbuf_empty_slots(dev); - if (slots < 0) - return -EOVERFLOW; - - len = buf->size - cb->buf_idx; - msg_slots = mei_data2slots(len); - mei_hdr.host_addr = mei_cl_host_addr(cl); mei_hdr.me_addr = mei_cl_me_id(cl); mei_hdr.reserved = 0; + mei_hdr.msg_complete = 0; mei_hdr.internal = cb->internal; - if ((u32)slots >= msg_slots) { + len = buf->size - cb->buf_idx; + hbuf_slots = mei_hbuf_empty_slots(dev); + if (hbuf_slots < 0) { + rets = -EOVERFLOW; + goto err; + } + hbuf_len = mei_slots2data(hbuf_slots) - sizeof(struct mei_msg_hdr); + + /** + * Split the message only if we can write the whole host buffer + * otherwise wait for next time the host buffer is empty. + */ + if (hbuf_len >= len) { mei_hdr.length = len; mei_hdr.msg_complete = 1; - /* Split the message only if we can write the whole host buffer */ - } else if ((u32)slots == dev->hbuf_depth) { - msg_slots = slots; - len = mei_slots2data(slots) - sizeof(struct mei_msg_hdr); - mei_hdr.length = len; - mei_hdr.msg_complete = 0; + } else if ((u32)hbuf_slots == mei_hbuf_depth(dev)) { + mei_hdr.length = hbuf_len; } else { - /* wait for next time the host buffer is empty */ return 0; } @@ -1650,6 +1651,8 @@ ssize_t mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb) struct mei_msg_data *buf; struct mei_msg_hdr mei_hdr; size_t len; + size_t hbuf_len; + int hbuf_slots; ssize_t rets; bool blocking; @@ -1692,19 +1695,25 @@ ssize_t mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb) rets = len; goto out; } + if (!mei_hbuf_acquire(dev)) { cl_dbg(dev, cl, "Cannot acquire the host buffer: not sending.\n"); rets = len; goto out; } - /* Check for a maximum length */ - if (len > mei_hbuf_max_len(dev)) { - mei_hdr.length = mei_hbuf_max_len(dev); - mei_hdr.msg_complete = 0; - } else { + hbuf_slots = mei_hbuf_empty_slots(dev); + if (hbuf_slots < 0) { + rets = -EOVERFLOW; + goto out; + } + + hbuf_len = mei_slots2data(hbuf_slots) - sizeof(struct mei_msg_hdr); + if (hbuf_len >= len) { mei_hdr.length = len; mei_hdr.msg_complete = 1; + } else { + mei_hdr.length = hbuf_len; } rets = mei_write_message(dev, &mei_hdr, buf->data); diff --git a/drivers/misc/mei/hw-me.c b/drivers/misc/mei/hw-me.c index 016b7c956f18..c50671cf47eb 100644 --- a/drivers/misc/mei/hw-me.c +++ b/drivers/misc/mei/hw-me.c @@ -228,7 +228,7 @@ static void mei_me_hw_config(struct mei_device *dev) /* Doesn't change in runtime */ hcsr = mei_hcsr_read(dev); - dev->hbuf_depth = (hcsr & H_CBD) >> 24; + hw->hbuf_depth = (hcsr & H_CBD) >> 24; reg = 0; pci_read_config_dword(pdev, PCI_CFG_HFS_1, ®); @@ -490,28 +490,31 @@ static bool mei_me_hbuf_is_empty(struct mei_device *dev) */ static int mei_me_hbuf_empty_slots(struct mei_device *dev) { + struct mei_me_hw *hw = to_me_hw(dev); unsigned char filled_slots, empty_slots; filled_slots = mei_hbuf_filled_slots(dev); - empty_slots = dev->hbuf_depth - filled_slots; + empty_slots = hw->hbuf_depth - filled_slots; /* check for overflow */ - if (filled_slots > dev->hbuf_depth) + if (filled_slots > hw->hbuf_depth) return -EOVERFLOW; return empty_slots; } /** - * mei_me_hbuf_max_len - returns size of hw buffer. + * mei_me_hbuf_depth - returns depth of the hw buffer. * * @dev: the device structure * - * Return: size of hw buffer in bytes + * Return: size of hw buffer in slots */ -static size_t mei_me_hbuf_max_len(const struct mei_device *dev) +static u32 mei_me_hbuf_depth(const struct mei_device *dev) { - return mei_slots2data(dev->hbuf_depth) - sizeof(struct mei_msg_hdr); + struct mei_me_hw *hw = to_me_hw(dev); + + return hw->hbuf_depth; } @@ -1317,7 +1320,7 @@ static const struct mei_hw_ops mei_me_hw_ops = { .hbuf_free_slots = mei_me_hbuf_empty_slots, .hbuf_is_ready = mei_me_hbuf_is_empty, - .hbuf_max_len = mei_me_hbuf_max_len, + .hbuf_depth = mei_me_hbuf_depth, .write = mei_me_hbuf_write, diff --git a/drivers/misc/mei/hw-me.h b/drivers/misc/mei/hw-me.h index 67892533576e..0c6fe71d1212 100644 --- a/drivers/misc/mei/hw-me.h +++ b/drivers/misc/mei/hw-me.h @@ -52,12 +52,14 @@ struct mei_cfg { * @mem_addr: io memory address * @pg_state: power gating state * @d0i3_supported: di03 support + * @hbuf_depth: depth of hardware host/write buffer in slots */ struct mei_me_hw { const struct mei_cfg *cfg; void __iomem *mem_addr; enum mei_pg_state pg_state; bool d0i3_supported; + u8 hbuf_depth; }; #define to_me_hw(dev) (struct mei_me_hw *)((dev)->hw) diff --git a/drivers/misc/mei/hw-txe.c b/drivers/misc/mei/hw-txe.c index 0facd823634e..7e2026894e9f 100644 --- a/drivers/misc/mei/hw-txe.c +++ b/drivers/misc/mei/hw-txe.c @@ -31,6 +31,7 @@ #include "mei-trace.h" +#define TXE_HBUF_DEPTH (PAYLOAD_SIZE / MEI_SLOT_SIZE) /** * mei_txe_reg_read - Reads 32bit data from the txe device @@ -681,9 +682,6 @@ static void mei_txe_hw_config(struct mei_device *dev) struct mei_txe_hw *hw = to_txe_hw(dev); - /* Doesn't change in runtime */ - dev->hbuf_depth = PAYLOAD_SIZE / MEI_SLOT_SIZE; - hw->aliveness = mei_txe_aliveness_get(dev); hw->readiness = mei_txe_readiness_get(dev); @@ -710,7 +708,7 @@ static int mei_txe_write(struct mei_device *dev, unsigned long rem; unsigned long length; unsigned long i; - u32 slots = dev->hbuf_depth; + u32 slots = TXE_HBUF_DEPTH; u32 *reg_buf = (u32 *)buf; u32 dw_cnt; @@ -762,15 +760,15 @@ static int mei_txe_write(struct mei_device *dev, } /** - * mei_txe_hbuf_max_len - mimics the me hbuf circular buffer + * mei_txe_hbuf_depth - mimics the me hbuf circular buffer * * @dev: the device structure * - * Return: the PAYLOAD_SIZE - header size + * Return: the TXE_HBUF_DEPTH */ -static size_t mei_txe_hbuf_max_len(const struct mei_device *dev) +static u32 mei_txe_hbuf_depth(const struct mei_device *dev) { - return PAYLOAD_SIZE - sizeof(struct mei_msg_hdr); + return TXE_HBUF_DEPTH; } /** @@ -778,7 +776,7 @@ static size_t mei_txe_hbuf_max_len(const struct mei_device *dev) * * @dev: the device structure * - * Return: always hbuf_depth + * Return: always TXE_HBUF_DEPTH */ static int mei_txe_hbuf_empty_slots(struct mei_device *dev) { @@ -797,7 +795,7 @@ static int mei_txe_hbuf_empty_slots(struct mei_device *dev) static int mei_txe_count_full_read_slots(struct mei_device *dev) { /* read buffers has static size */ - return PAYLOAD_SIZE / MEI_SLOT_SIZE; + return TXE_HBUF_DEPTH; } /** @@ -1140,7 +1138,7 @@ irqreturn_t mei_txe_irq_thread_handler(int irq, void *dev_id) /* Input Ready: Detection if host can write to SeC */ if (test_and_clear_bit(TXE_INTR_IN_READY_BIT, &hw->intr_cause)) { dev->hbuf_is_ready = true; - hw->slots = dev->hbuf_depth; + hw->slots = TXE_HBUF_DEPTH; } if (hw->aliveness && dev->hbuf_is_ready) { @@ -1186,7 +1184,7 @@ static const struct mei_hw_ops mei_txe_hw_ops = { .hbuf_free_slots = mei_txe_hbuf_empty_slots, .hbuf_is_ready = mei_txe_is_input_ready, - .hbuf_max_len = mei_txe_hbuf_max_len, + .hbuf_depth = mei_txe_hbuf_depth, .write = mei_txe_write, diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index 897126dca5d0..fa543dcfc111 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -29,7 +29,6 @@ #define MEI_SLOT_SIZE sizeof(u32) #define MEI_RD_MSG_BUF_SIZE (128 * MEI_SLOT_SIZE) - /* * Number of Maximum MEI Clients */ @@ -271,7 +270,7 @@ struct mei_cl { * * @hbuf_free_slots : query for write buffer empty slots * @hbuf_is_ready : query if write buffer is empty - * @hbuf_max_len : query for write buffer max len + * @hbuf_depth : query for write buffer depth * * @write : write a message to FW * @@ -301,7 +300,7 @@ struct mei_hw_ops { int (*hbuf_free_slots)(struct mei_device *dev); bool (*hbuf_is_ready)(struct mei_device *dev); - size_t (*hbuf_max_len)(const struct mei_device *dev); + u32 (*hbuf_depth)(const struct mei_device *dev); int (*write)(struct mei_device *dev, struct mei_msg_hdr *hdr, const unsigned char *buf); @@ -411,7 +410,6 @@ struct mei_fw_version { * @rd_msg_buf : control messages buffer * @rd_msg_hdr : read message header storage * - * @hbuf_depth : depth of hardware host/write buffer is slots * @hbuf_is_ready : query if the host host/write buffer is ready * * @version : HBM protocol version in use @@ -489,7 +487,6 @@ struct mei_device { u32 rd_msg_hdr; /* write buffer */ - u8 hbuf_depth; bool hbuf_is_ready; struct hbm_version version; @@ -655,9 +652,9 @@ static inline int mei_hbuf_empty_slots(struct mei_device *dev) return dev->ops->hbuf_free_slots(dev); } -static inline size_t mei_hbuf_max_len(const struct mei_device *dev) +static inline u32 mei_hbuf_depth(const struct mei_device *dev) { - return dev->ops->hbuf_max_len(dev); + return dev->ops->hbuf_depth(dev); } static inline int mei_write_message(struct mei_device *dev, -- cgit v1.2.3 From a1c4d08b6b95a0e296285ee53cc1438b3f168f98 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Mon, 23 Jul 2018 13:21:24 +0300 Subject: mei: add mei_msg_hdr_init wrapper. Wrap the mei header boilerplate initialization code in mei_msg_hdr_init function. On the way remove 'completed' field from mei_cl_cb structure as this information is already included in the header and is local to particular fragment. Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/client.c | 33 ++++++++++++++++++++------------- drivers/misc/mei/mei_dev.h | 2 -- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c index ca917b84ca5e..207d2f5d5702 100644 --- a/drivers/misc/mei/client.c +++ b/drivers/misc/mei/client.c @@ -1539,6 +1539,22 @@ nortpm: return rets; } +/** + * mei_msg_hdr_init - initialize mei message header + * + * @mei_hdr: mei message header + * @cb: message callback structure + */ +static void mei_msg_hdr_init(struct mei_msg_hdr *mei_hdr, struct mei_cl_cb *cb) +{ + mei_hdr->host_addr = mei_cl_host_addr(cb->cl); + mei_hdr->me_addr = mei_cl_me_id(cb->cl); + mei_hdr->length = 0; + mei_hdr->reserved = 0; + mei_hdr->msg_complete = 0; + mei_hdr->internal = cb->internal; +} + /** * mei_cl_irq_write - write a message to device * from the interrupt thread context @@ -1579,12 +1595,6 @@ int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb, return 0; } - mei_hdr.host_addr = mei_cl_host_addr(cl); - mei_hdr.me_addr = mei_cl_me_id(cl); - mei_hdr.reserved = 0; - mei_hdr.msg_complete = 0; - mei_hdr.internal = cb->internal; - len = buf->size - cb->buf_idx; hbuf_slots = mei_hbuf_empty_slots(dev); if (hbuf_slots < 0) { @@ -1593,6 +1603,8 @@ int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb, } hbuf_len = mei_slots2data(hbuf_slots) - sizeof(struct mei_msg_hdr); + mei_msg_hdr_init(&mei_hdr, cb); + /** * Split the message only if we can write the whole host buffer * otherwise wait for next time the host buffer is empty. @@ -1616,7 +1628,6 @@ 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 (first_chunk) { if (mei_cl_tx_flow_ctrl_creds_reduce(cl)) { @@ -1680,16 +1691,13 @@ ssize_t mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb) cb->buf_idx = 0; cl->writing_state = MEI_IDLE; - mei_hdr.host_addr = mei_cl_host_addr(cl); - mei_hdr.me_addr = mei_cl_me_id(cl); - mei_hdr.reserved = 0; - mei_hdr.msg_complete = 0; - mei_hdr.internal = cb->internal; rets = mei_cl_tx_flow_ctrl_creds(cl); if (rets < 0) goto err; + mei_msg_hdr_init(&mei_hdr, cb); + if (rets == 0) { cl_dbg(dev, cl, "No flow control credentials: not sending.\n"); rets = len; @@ -1726,7 +1734,6 @@ ssize_t mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb) 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/mei_dev.h b/drivers/misc/mei/mei_dev.h index fa543dcfc111..e9f37085a628 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -175,7 +175,6 @@ struct mei_cl; * @status: io status of the cb * @internal: communication between driver and FW flag * @blocking: transmission blocking mode - * @completed: the transfer or reception has completed */ struct mei_cl_cb { struct list_head list; @@ -187,7 +186,6 @@ struct mei_cl_cb { int status; u32 internal:1; u32 blocking:1; - u32 completed:1; }; /** -- cgit v1.2.3 From 1e81c57b598307c5611410fd357554ffacc6290d Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Mon, 23 Jul 2018 14:47:23 -0700 Subject: android: binder_alloc: Include asm/cacheflush.h after linux/ include files If asm/cacheflush.h is included first, the following build warnings are seen with sparc32 builds. In file included from ./arch/sparc/include/asm/cacheflush.h:11:0, from drivers/android/binder_alloc.c:20: ./arch/sparc/include/asm/cacheflush_32.h:40:37: warning: 'struct page' declared inside parameter list Moving the asm/ include after linux/ includes fixes the problem. Suggested-by: Linus Torvalds Signed-off-by: Guenter Roeck Signed-off-by: Greg Kroah-Hartman --- drivers/android/binder_alloc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/android/binder_alloc.c b/drivers/android/binder_alloc.c index 2628806c64a2..2c258dcf9d72 100644 --- a/drivers/android/binder_alloc.c +++ b/drivers/android/binder_alloc.c @@ -17,7 +17,6 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt -#include #include #include #include @@ -28,6 +27,7 @@ #include #include #include +#include #include "binder_alloc.h" #include "binder_trace.h" -- cgit v1.2.3 From f371a7c17aaa47dd7aedc89ef3cd1d5638c03f5e Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Mon, 23 Jul 2018 14:41:38 -0700 Subject: android: binder: Include asm/cacheflush.h after linux/ include files If asm/cacheflush.h is included first, the following build warnings are seen with sparc32 builds. In file included from arch/sparc/include/asm/cacheflush.h:11:0, from drivers/android/binder.c:54: arch/sparc/include/asm/cacheflush_32.h:40:37: warning: 'struct page' declared inside parameter list will not be visible outside of this definition or declaration Moving the asm/ include after linux/ includes solves the problem. Suggested-by: Linus Torvalds Signed-off-by: Guenter Roeck Signed-off-by: Greg Kroah-Hartman --- drivers/android/binder.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/android/binder.c b/drivers/android/binder.c index 95283f3bb51c..1cc2fa16af8b 100644 --- a/drivers/android/binder.c +++ b/drivers/android/binder.c @@ -51,7 +51,6 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt -#include #include #include #include @@ -73,6 +72,9 @@ #include #include + +#include + #include "binder_alloc.h" #include "binder_trace.h" -- cgit v1.2.3 From 375cac70100b58ce8f4737ec530bfbdcb3086964 Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Wed, 25 Jul 2018 14:57:21 +1000 Subject: fsi: master-ast-cf: Mask unused bits in RTAG/RCRC Then reading the RTAG/RCRC "registers" from the coprocessor after a command is complete, mask out the top bits, only keep the relevant bits. Microcode v5 will leave garbage in those top bits as a result of a performance optimization. Signed-off-by: Benjamin Herrenschmidt --- --- drivers/fsi/fsi-master-ast-cf.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/fsi/fsi-master-ast-cf.c b/drivers/fsi/fsi-master-ast-cf.c index c4a35579bfdc..b59abcf3ee32 100644 --- a/drivers/fsi/fsi-master-ast-cf.c +++ b/drivers/fsi/fsi-master-ast-cf.c @@ -377,8 +377,8 @@ static int send_request(struct fsi_master_acf *master, struct fsi_msg *cmd, static int read_copro_response(struct fsi_master_acf *master, uint8_t size, uint32_t *response, u8 *tag) { - uint8_t rtag = ioread8(master->sram + STAT_RTAG); - uint8_t rcrc = ioread8(master->sram + STAT_RCRC); + uint8_t rtag = ioread8(master->sram + STAT_RTAG) & 0xf; + uint8_t rcrc = ioread8(master->sram + STAT_RCRC) & 0xf; uint32_t rdata = 0; uint32_t crc; uint8_t ack; -- cgit v1.2.3 From c356915ebc9ddb8303feeab1e3465170285558b5 Mon Sep 17 00:00:00 2001 From: Nathan Ciobanu Date: Wed, 25 Jul 2018 11:03:15 +0300 Subject: thunderbolt: Fix small typo in variable name Fixes small variable name typo and the associated checkpatch spelling warning. Signed-off-by: Nathan Ciobanu Signed-off-by: Mika Westerberg Signed-off-by: Greg Kroah-Hartman --- drivers/thunderbolt/tb_regs.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/thunderbolt/tb_regs.h b/drivers/thunderbolt/tb_regs.h index 5d94142afda6..693b0353c3fe 100644 --- a/drivers/thunderbolt/tb_regs.h +++ b/drivers/thunderbolt/tb_regs.h @@ -202,7 +202,7 @@ struct tb_regs_port_header { /* DWORD 5 */ u32 max_in_hop_id:11; u32 max_out_hop_id:11; - u32 __unkown4:10; + u32 __unknown4:10; /* DWORD 6 */ u32 __unknown5; /* DWORD 7 */ -- cgit v1.2.3 From dba3caf6210397635714c7f46129d8c0c9af2512 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Wed, 25 Jul 2018 11:03:16 +0300 Subject: thunderbolt: Use 64-bit DMA mask if supported by the platform PCI defaults to 32-bit DMA mask but this device is capable of full 64-bit addressing, so make sure we first try 64-bit DMA mask before falling back to the default 32-bit. Signed-off-by: Mika Westerberg Signed-off-by: Greg Kroah-Hartman --- drivers/thunderbolt/nhi.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index f5a33e88e676..e3b7695fe37e 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -1015,6 +1015,14 @@ static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id) spin_lock_init(&nhi->lock); + res = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); + if (res) + res = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); + if (res) { + dev_err(&pdev->dev, "failed to set DMA mask\n"); + return res; + } + pci_set_master(pdev); tb = icm_probe(nhi); -- cgit v1.2.3 From fdd92e89a492142a113f270dcd7e490805922b71 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Wed, 25 Jul 2018 11:03:17 +0300 Subject: thunderbolt: Do not unnecessarily call ICM get route This command is not really fast and can make resume time slower. We only need to get route again if the link was changed and during initial device connected message. Signed-off-by: Mika Westerberg Signed-off-by: Greg Kroah-Hartman --- drivers/thunderbolt/icm.c | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/drivers/thunderbolt/icm.c b/drivers/thunderbolt/icm.c index 500911f16498..ad4eeaa59b16 100644 --- a/drivers/thunderbolt/icm.c +++ b/drivers/thunderbolt/icm.c @@ -534,20 +534,13 @@ icm_fr_device_connected(struct tb *tb, const struct icm_pkg_header *hdr) return; } - ret = icm->get_route(tb, link, depth, &route); - if (ret) { - tb_err(tb, "failed to find route string for switch at %u.%u\n", - link, depth); - return; - } - sw = tb_switch_find_by_uuid(tb, &pkg->ep_uuid); if (sw) { u8 phy_port, sw_phy_port; parent_sw = tb_to_switch(sw->dev.parent); - sw_phy_port = phy_port_from_route(tb_route(sw), sw->depth); - phy_port = phy_port_from_route(route, depth); + sw_phy_port = tb_phy_port_from_link(sw->link); + phy_port = tb_phy_port_from_link(link); /* * On resume ICM will send us connected events for the @@ -559,6 +552,22 @@ icm_fr_device_connected(struct tb *tb, const struct icm_pkg_header *hdr) */ if (sw->depth == depth && sw_phy_port == phy_port && !!sw->authorized == authorized) { + /* + * It was enumerated through another link so update + * route string accordingly. + */ + if (sw->link != link) { + ret = icm->get_route(tb, link, depth, &route); + if (ret) { + tb_err(tb, "failed to update route string for switch at %u.%u\n", + link, depth); + tb_switch_put(sw); + return; + } + } else { + route = tb_route(sw); + } + update_switch(parent_sw, sw, route, pkg->connection_id, pkg->connection_key, link, depth, boot); tb_switch_put(sw); @@ -607,6 +616,14 @@ icm_fr_device_connected(struct tb *tb, const struct icm_pkg_header *hdr) return; } + ret = icm->get_route(tb, link, depth, &route); + if (ret) { + tb_err(tb, "failed to find route string for switch at %u.%u\n", + link, depth); + tb_switch_put(parent_sw); + return; + } + add_switch(parent_sw, route, &pkg->ep_uuid, pkg->connection_id, pkg->connection_key, link, depth, security_level, authorized, boot); -- cgit v1.2.3 From 84db68587651d453a9b5e901c787a49866e34b1e Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Wed, 25 Jul 2018 11:03:18 +0300 Subject: thunderbolt: No need to take tb->lock in domain suspend/complete If the connection manager implementation needs to touch the domain structures it ought to take the lock itself. Currently only ICM implements these hooks and it does not need the lock because we there will be no notifications before driver ready message is sent to it. Signed-off-by: Mika Westerberg Signed-off-by: Greg Kroah-Hartman --- drivers/thunderbolt/domain.c | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/drivers/thunderbolt/domain.c b/drivers/thunderbolt/domain.c index a923ebdeb73c..e377128663e9 100644 --- a/drivers/thunderbolt/domain.c +++ b/drivers/thunderbolt/domain.c @@ -509,26 +509,13 @@ int tb_domain_resume_noirq(struct tb *tb) int tb_domain_suspend(struct tb *tb) { - int ret; - - mutex_lock(&tb->lock); - if (tb->cm_ops->suspend) { - ret = tb->cm_ops->suspend(tb); - if (ret) { - mutex_unlock(&tb->lock); - return ret; - } - } - mutex_unlock(&tb->lock); - return 0; + return tb->cm_ops->suspend ? tb->cm_ops->suspend(tb) : 0; } void tb_domain_complete(struct tb *tb) { - mutex_lock(&tb->lock); if (tb->cm_ops->complete) tb->cm_ops->complete(tb); - mutex_unlock(&tb->lock); } /** -- cgit v1.2.3 From d04522fa08905907d2b5ccf28b7793353646754b Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Wed, 25 Jul 2018 11:03:19 +0300 Subject: thunderbolt: Use correct ICM commands in system suspend The correct way to put the ICM into suspend state is to send it NHI_MAILBOX_DRV_UNLOADS mailbox command. NHI_MAILBOX_SAVE_DEVS is not needed on Intel Titan Ridge so we can skip it. Signed-off-by: Mika Westerberg Signed-off-by: Greg Kroah-Hartman --- drivers/thunderbolt/icm.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/drivers/thunderbolt/icm.c b/drivers/thunderbolt/icm.c index ad4eeaa59b16..5ed3f8a31b0a 100644 --- a/drivers/thunderbolt/icm.c +++ b/drivers/thunderbolt/icm.c @@ -60,6 +60,7 @@ * @is_supported: Checks if we can support ICM on this controller * @get_mode: Read and return the ICM firmware mode (optional) * @get_route: Find a route string for given switch + * @save_devices: Ask ICM to save devices to ACL when suspending (optional) * @driver_ready: Send driver ready message to ICM * @device_connected: Handle device connected ICM message * @device_disconnected: Handle device disconnected ICM message @@ -76,6 +77,7 @@ struct icm { bool (*is_supported)(struct tb *tb); int (*get_mode)(struct tb *tb); int (*get_route)(struct tb *tb, u8 link, u8 depth, u64 *route); + void (*save_devices)(struct tb *tb); int (*driver_ready)(struct tb *tb, enum tb_security_level *security_level, size_t *nboot_acl); @@ -258,6 +260,11 @@ err_free: return ret; } +static void icm_fr_save_devices(struct tb *tb) +{ + nhi_mailbox_cmd(tb->nhi, NHI_MAILBOX_SAVE_DEVS, 0); +} + static int icm_fr_driver_ready(struct tb *tb, enum tb_security_level *security_level, size_t *nboot_acl) @@ -1665,13 +1672,12 @@ static int icm_driver_ready(struct tb *tb) static int icm_suspend(struct tb *tb) { - int ret; + struct icm *icm = tb_priv(tb); - ret = nhi_mailbox_cmd(tb->nhi, NHI_MAILBOX_SAVE_DEVS, 0); - if (ret) - tb_info(tb, "Ignoring mailbox command error (%d) in %s\n", - ret, __func__); + if (icm->save_devices) + icm->save_devices(tb); + nhi_mailbox_cmd(tb->nhi, NHI_MAILBOX_DRV_UNLOADS, 0); return 0; } @@ -1879,6 +1885,7 @@ struct tb *icm_probe(struct tb_nhi *nhi) case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_NHI: icm->is_supported = icm_fr_is_supported; icm->get_route = icm_fr_get_route; + icm->save_devices = icm_fr_save_devices; icm->driver_ready = icm_fr_driver_ready; icm->device_connected = icm_fr_device_connected; icm->device_disconnected = icm_fr_device_disconnected; @@ -1896,6 +1903,7 @@ struct tb *icm_probe(struct tb_nhi *nhi) icm->is_supported = icm_ar_is_supported; icm->get_mode = icm_ar_get_mode; icm->get_route = icm_ar_get_route; + icm->save_devices = icm_fr_save_devices; icm->driver_ready = icm_ar_driver_ready; icm->device_connected = icm_fr_device_connected; icm->device_disconnected = icm_fr_device_disconnected; -- cgit v1.2.3 From fa3af1cb1ec073265c9c87ec44faf006f2f12d96 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Wed, 25 Jul 2018 11:03:21 +0300 Subject: thunderbolt: Remove redundant variable 'approved' Variable 'approved' is being assigned but is never used hence it is redundant and can be removed. Cleans up clang warning: warning: variable 'approved' set but not used [-Wunused-but-set-variable] Signed-off-by: Colin Ian King Signed-off-by: Mika Westerberg Signed-off-by: Greg Kroah-Hartman --- drivers/thunderbolt/icm.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/thunderbolt/icm.c b/drivers/thunderbolt/icm.c index 5ed3f8a31b0a..e0930dbbcf47 100644 --- a/drivers/thunderbolt/icm.c +++ b/drivers/thunderbolt/icm.c @@ -674,7 +674,6 @@ icm_fr_xdomain_connected(struct tb *tb, const struct icm_pkg_header *hdr) struct tb_xdomain *xd; struct tb_switch *sw; u8 link, depth; - bool approved; u64 route; /* @@ -688,7 +687,6 @@ icm_fr_xdomain_connected(struct tb *tb, const struct icm_pkg_header *hdr) link = pkg->link_info & ICM_LINK_INFO_LINK_MASK; depth = (pkg->link_info & ICM_LINK_INFO_DEPTH_MASK) >> ICM_LINK_INFO_DEPTH_SHIFT; - approved = pkg->link_info & ICM_LINK_INFO_APPROVED; if (link > ICM_MAX_LINK || depth > ICM_MAX_DEPTH) { tb_warn(tb, "invalid topology %u.%u, ignoring\n", link, depth); -- cgit v1.2.3 From 2d8ff0b586fb1c5bd81a3ab286dcc6bbc432044e Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Wed, 25 Jul 2018 11:48:39 +0300 Subject: thunderbolt: Add support for runtime PM When Thunderbolt host controller is set to RTD3 mode (Runtime D3) it is present all the time. Because of this it is important to runtime suspend the controller whenever possible. In case of ICM we have following rules which all needs to be true before the host controller can be put to D3: - The controller firmware reports to support RTD3 - All the connected devices announce support for RTD3 - There is no active XDomain connection Implement this using standard Linux runtime PM APIs so that when all the children devices are runtime suspended, the Thunderbolt host controller PCI device is runtime suspended as well. The ICM firmware then starts powering down power domains towards RTD3 but it can prevent this if it detects that there is an active Display Port stream (this is not visible to the software, though). The Thunderbolt host controller will be runtime resumed either when there is a remote wake event (device is connected or disconnected), or when there is access from userspace that requires hardware access. Signed-off-by: Mika Westerberg Signed-off-by: Greg Kroah-Hartman --- drivers/thunderbolt/domain.c | 42 ++++++++++++++- drivers/thunderbolt/icm.c | 119 +++++++++++++++++++++++++++++++++++++----- drivers/thunderbolt/nhi.c | 38 +++++++++++++- drivers/thunderbolt/switch.c | 65 +++++++++++++++++++++-- drivers/thunderbolt/tb.h | 10 ++++ drivers/thunderbolt/tb_msgs.h | 4 ++ drivers/thunderbolt/xdomain.c | 18 +++++++ 7 files changed, 276 insertions(+), 20 deletions(-) diff --git a/drivers/thunderbolt/domain.c b/drivers/thunderbolt/domain.c index e377128663e9..092381e2accf 100644 --- a/drivers/thunderbolt/domain.c +++ b/drivers/thunderbolt/domain.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -132,6 +133,8 @@ static ssize_t boot_acl_show(struct device *dev, struct device_attribute *attr, if (!uuids) return -ENOMEM; + pm_runtime_get_sync(&tb->dev); + if (mutex_lock_interruptible(&tb->lock)) { ret = -ERESTARTSYS; goto out; @@ -153,7 +156,10 @@ static ssize_t boot_acl_show(struct device *dev, struct device_attribute *attr, } out: + pm_runtime_mark_last_busy(&tb->dev); + pm_runtime_put_autosuspend(&tb->dev); kfree(uuids); + return ret; } @@ -208,9 +214,11 @@ static ssize_t boot_acl_store(struct device *dev, struct device_attribute *attr, goto err_free_acl; } + pm_runtime_get_sync(&tb->dev); + if (mutex_lock_interruptible(&tb->lock)) { ret = -ERESTARTSYS; - goto err_free_acl; + goto err_rpm_put; } ret = tb->cm_ops->set_boot_acl(tb, acl, tb->nboot_acl); if (!ret) { @@ -219,6 +227,9 @@ static ssize_t boot_acl_store(struct device *dev, struct device_attribute *attr, } mutex_unlock(&tb->lock); +err_rpm_put: + pm_runtime_mark_last_busy(&tb->dev); + pm_runtime_put_autosuspend(&tb->dev); err_free_acl: kfree(acl); err_free_str: @@ -430,6 +441,13 @@ int tb_domain_add(struct tb *tb) /* This starts event processing */ mutex_unlock(&tb->lock); + pm_runtime_no_callbacks(&tb->dev); + pm_runtime_set_active(&tb->dev); + pm_runtime_enable(&tb->dev); + pm_runtime_set_autosuspend_delay(&tb->dev, TB_AUTOSUSPEND_DELAY); + pm_runtime_mark_last_busy(&tb->dev); + pm_runtime_use_autosuspend(&tb->dev); + return 0; err_domain_del: @@ -518,6 +536,28 @@ void tb_domain_complete(struct tb *tb) tb->cm_ops->complete(tb); } +int tb_domain_runtime_suspend(struct tb *tb) +{ + if (tb->cm_ops->runtime_suspend) { + int ret = tb->cm_ops->runtime_suspend(tb); + if (ret) + return ret; + } + tb_ctl_stop(tb->ctl); + return 0; +} + +int tb_domain_runtime_resume(struct tb *tb) +{ + tb_ctl_start(tb->ctl); + if (tb->cm_ops->runtime_resume) { + int ret = tb->cm_ops->runtime_resume(tb); + if (ret) + return ret; + } + return 0; +} + /** * tb_domain_approve_switch() - Approve switch * @tb: Domain the switch belongs to diff --git a/drivers/thunderbolt/icm.c b/drivers/thunderbolt/icm.c index e0930dbbcf47..e1e264a9a4c7 100644 --- a/drivers/thunderbolt/icm.c +++ b/drivers/thunderbolt/icm.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -57,6 +58,7 @@ * (only set when @upstream_port is not %NULL) * @safe_mode: ICM is in safe mode * @max_boot_acl: Maximum number of preboot ACL entries (%0 if not supported) + * @rpm: Does the controller support runtime PM (RTD3) * @is_supported: Checks if we can support ICM on this controller * @get_mode: Read and return the ICM firmware mode (optional) * @get_route: Find a route string for given switch @@ -74,13 +76,14 @@ struct icm { size_t max_boot_acl; int vnd_cap; bool safe_mode; + bool rpm; bool (*is_supported)(struct tb *tb); int (*get_mode)(struct tb *tb); int (*get_route)(struct tb *tb, u8 link, u8 depth, u64 *route); void (*save_devices)(struct tb *tb); int (*driver_ready)(struct tb *tb, enum tb_security_level *security_level, - size_t *nboot_acl); + size_t *nboot_acl, bool *rpm); void (*device_connected)(struct tb *tb, const struct icm_pkg_header *hdr); void (*device_disconnected)(struct tb *tb, @@ -97,6 +100,47 @@ struct icm_notification { struct tb *tb; }; +struct ep_name_entry { + u8 len; + u8 type; + u8 data[0]; +}; + +#define EP_NAME_INTEL_VSS 0x10 + +/* Intel Vendor specific structure */ +struct intel_vss { + u16 vendor; + u16 model; + u8 mc; + u8 flags; + u16 pci_devid; + u32 nvm_version; +}; + +#define INTEL_VSS_FLAGS_RTD3 BIT(0) + +static const struct intel_vss *parse_intel_vss(const void *ep_name, size_t size) +{ + const void *end = ep_name + size; + + while (ep_name < end) { + const struct ep_name_entry *ep = ep_name; + + if (!ep->len) + break; + if (ep_name + ep->len > end) + break; + + if (ep->type == EP_NAME_INTEL_VSS) + return (const struct intel_vss *)ep->data; + + ep_name += ep->len; + } + + return NULL; +} + static inline struct tb *icm_to_tb(struct icm *icm) { return ((void *)icm - sizeof(struct tb)); @@ -267,7 +311,7 @@ static void icm_fr_save_devices(struct tb *tb) static int icm_fr_driver_ready(struct tb *tb, enum tb_security_level *security_level, - size_t *nboot_acl) + size_t *nboot_acl, bool *rpm) { struct icm_fr_pkg_driver_ready_response reply; struct icm_pkg_driver_ready request = { @@ -417,15 +461,19 @@ static int icm_fr_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd) } static void add_switch(struct tb_switch *parent_sw, u64 route, - const uuid_t *uuid, u8 connection_id, u8 connection_key, + const uuid_t *uuid, const u8 *ep_name, + size_t ep_name_size, u8 connection_id, u8 connection_key, u8 link, u8 depth, enum tb_security_level security_level, bool authorized, bool boot) { + const struct intel_vss *vss; struct tb_switch *sw; + pm_runtime_get_sync(&parent_sw->dev); + sw = tb_switch_alloc(parent_sw->tb, &parent_sw->dev, route); if (!sw) - return; + goto out; sw->uuid = kmemdup(uuid, sizeof(*uuid), GFP_KERNEL); sw->connection_id = connection_id; @@ -436,6 +484,10 @@ static void add_switch(struct tb_switch *parent_sw, u64 route, sw->security_level = security_level; sw->boot = boot; + vss = parse_intel_vss(ep_name, ep_name_size); + if (vss) + sw->rpm = !!(vss->flags & INTEL_VSS_FLAGS_RTD3); + /* Link the two switches now */ tb_port_at(route, parent_sw)->remote = tb_upstream_port(sw); tb_upstream_port(sw)->remote = tb_port_at(route, parent_sw); @@ -443,8 +495,11 @@ static void add_switch(struct tb_switch *parent_sw, u64 route, if (tb_switch_add(sw)) { tb_port_at(tb_route(sw), parent_sw)->remote = NULL; tb_switch_put(sw); - return; } + +out: + pm_runtime_mark_last_busy(&parent_sw->dev); + pm_runtime_put_autosuspend(&parent_sw->dev); } static void update_switch(struct tb_switch *parent_sw, struct tb_switch *sw, @@ -484,9 +539,11 @@ static void add_xdomain(struct tb_switch *sw, u64 route, { struct tb_xdomain *xd; + pm_runtime_get_sync(&sw->dev); + xd = tb_xdomain_alloc(sw->tb, &sw->dev, route, local_uuid, remote_uuid); if (!xd) - return; + goto out; xd->link = link; xd->depth = depth; @@ -494,6 +551,10 @@ static void add_xdomain(struct tb_switch *sw, u64 route, tb_port_at(route, sw)->xdomain = xd; tb_xdomain_add(xd); + +out: + pm_runtime_mark_last_busy(&sw->dev); + pm_runtime_put_autosuspend(&sw->dev); } static void update_xdomain(struct tb_xdomain *xd, u64 route, u8 link) @@ -631,7 +692,8 @@ icm_fr_device_connected(struct tb *tb, const struct icm_pkg_header *hdr) return; } - add_switch(parent_sw, route, &pkg->ep_uuid, pkg->connection_id, + add_switch(parent_sw, route, &pkg->ep_uuid, (const u8 *)pkg->ep_name, + sizeof(pkg->ep_name), pkg->connection_id, pkg->connection_key, link, depth, security_level, authorized, boot); @@ -779,7 +841,7 @@ icm_fr_xdomain_disconnected(struct tb *tb, const struct icm_pkg_header *hdr) static int icm_tr_driver_ready(struct tb *tb, enum tb_security_level *security_level, - size_t *nboot_acl) + size_t *nboot_acl, bool *rpm) { struct icm_tr_pkg_driver_ready_response reply; struct icm_pkg_driver_ready request = { @@ -798,6 +860,9 @@ icm_tr_driver_ready(struct tb *tb, enum tb_security_level *security_level, if (nboot_acl) *nboot_acl = (reply.info & ICM_TR_INFO_BOOT_ACL_MASK) >> ICM_TR_INFO_BOOT_ACL_SHIFT; + if (rpm) + *rpm = !!(reply.hdr.flags & ICM_TR_FLAGS_RTD3); + return 0; } @@ -1027,7 +1092,8 @@ icm_tr_device_connected(struct tb *tb, const struct icm_pkg_header *hdr) return; } - add_switch(parent_sw, route, &pkg->ep_uuid, pkg->connection_id, + add_switch(parent_sw, route, &pkg->ep_uuid, (const u8 *)pkg->ep_name, + sizeof(pkg->ep_name), pkg->connection_id, 0, 0, 0, security_level, authorized, boot); tb_switch_put(parent_sw); @@ -1206,7 +1272,7 @@ static int icm_ar_get_mode(struct tb *tb) static int icm_ar_driver_ready(struct tb *tb, enum tb_security_level *security_level, - size_t *nboot_acl) + size_t *nboot_acl, bool *rpm) { struct icm_ar_pkg_driver_ready_response reply; struct icm_pkg_driver_ready request = { @@ -1225,6 +1291,9 @@ icm_ar_driver_ready(struct tb *tb, enum tb_security_level *security_level, if (nboot_acl && (reply.info & ICM_AR_INFO_BOOT_ACL_SUPPORTED)) *nboot_acl = (reply.info & ICM_AR_INFO_BOOT_ACL_MASK) >> ICM_AR_INFO_BOOT_ACL_SHIFT; + if (rpm) + *rpm = !!(reply.hdr.flags & ICM_AR_FLAGS_RTD3); + return 0; } @@ -1378,13 +1447,13 @@ static void icm_handle_event(struct tb *tb, enum tb_cfg_pkg_type type, static int __icm_driver_ready(struct tb *tb, enum tb_security_level *security_level, - size_t *nboot_acl) + size_t *nboot_acl, bool *rpm) { struct icm *icm = tb_priv(tb); unsigned int retries = 50; int ret; - ret = icm->driver_ready(tb, security_level, nboot_acl); + ret = icm->driver_ready(tb, security_level, nboot_acl, rpm); if (ret) { tb_err(tb, "failed to send driver ready to ICM\n"); return ret; @@ -1654,7 +1723,8 @@ static int icm_driver_ready(struct tb *tb) return 0; } - ret = __icm_driver_ready(tb, &tb->security_level, &tb->nboot_acl); + ret = __icm_driver_ready(tb, &tb->security_level, &tb->nboot_acl, + &icm->rpm); if (ret) return ret; @@ -1760,7 +1830,7 @@ static void icm_complete(struct tb *tb) * Now all existing children should be resumed, start events * from ICM to get updated status. */ - __icm_driver_ready(tb, NULL, NULL); + __icm_driver_ready(tb, NULL, NULL, NULL); /* * We do not get notifications of devices that have been @@ -1770,6 +1840,22 @@ static void icm_complete(struct tb *tb) queue_delayed_work(tb->wq, &icm->rescan_work, msecs_to_jiffies(500)); } +static int icm_runtime_suspend(struct tb *tb) +{ + nhi_mailbox_cmd(tb->nhi, NHI_MAILBOX_DRV_UNLOADS, 0); + return 0; +} + +static int icm_runtime_resume(struct tb *tb) +{ + /* + * We can reuse the same resume functionality than with system + * suspend. + */ + icm_complete(tb); + return 0; +} + static int icm_start(struct tb *tb) { struct icm *icm = tb_priv(tb); @@ -1788,6 +1874,7 @@ static int icm_start(struct tb *tb) * prevent root switch NVM upgrade on Macs for now. */ tb->root_switch->no_nvm_upgrade = x86_apple_machine; + tb->root_switch->rpm = icm->rpm; ret = tb_switch_add(tb->root_switch); if (ret) { @@ -1836,6 +1923,8 @@ static const struct tb_cm_ops icm_ar_ops = { .stop = icm_stop, .suspend = icm_suspend, .complete = icm_complete, + .runtime_suspend = icm_runtime_suspend, + .runtime_resume = icm_runtime_resume, .handle_event = icm_handle_event, .get_boot_acl = icm_ar_get_boot_acl, .set_boot_acl = icm_ar_set_boot_acl, @@ -1854,6 +1943,8 @@ static const struct tb_cm_ops icm_tr_ops = { .stop = icm_stop, .suspend = icm_suspend, .complete = icm_complete, + .runtime_suspend = icm_runtime_suspend, + .runtime_resume = icm_runtime_resume, .handle_event = icm_handle_event, .get_boot_acl = icm_ar_get_boot_acl, .set_boot_acl = icm_ar_set_boot_acl, diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index e3b7695fe37e..88cff05a1808 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -900,7 +900,32 @@ static void nhi_complete(struct device *dev) struct pci_dev *pdev = to_pci_dev(dev); struct tb *tb = pci_get_drvdata(pdev); - tb_domain_complete(tb); + /* + * If we were runtime suspended when system suspend started, + * schedule runtime resume now. It should bring the domain back + * to functional state. + */ + if (pm_runtime_suspended(&pdev->dev)) + pm_runtime_resume(&pdev->dev); + else + tb_domain_complete(tb); +} + +static int nhi_runtime_suspend(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct tb *tb = pci_get_drvdata(pdev); + + return tb_domain_runtime_suspend(tb); +} + +static int nhi_runtime_resume(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct tb *tb = pci_get_drvdata(pdev); + + nhi_enable_int_throttling(tb->nhi); + return tb_domain_runtime_resume(tb); } static void nhi_shutdown(struct tb_nhi *nhi) @@ -1048,6 +1073,11 @@ static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id) } pci_set_drvdata(pdev, tb); + pm_runtime_allow(&pdev->dev); + pm_runtime_set_autosuspend_delay(&pdev->dev, TB_AUTOSUSPEND_DELAY); + pm_runtime_use_autosuspend(&pdev->dev); + pm_runtime_put_autosuspend(&pdev->dev); + return 0; } @@ -1056,6 +1086,10 @@ static void nhi_remove(struct pci_dev *pdev) struct tb *tb = pci_get_drvdata(pdev); struct tb_nhi *nhi = tb->nhi; + pm_runtime_get_sync(&pdev->dev); + pm_runtime_dont_use_autosuspend(&pdev->dev); + pm_runtime_forbid(&pdev->dev); + tb_domain_remove(tb); nhi_shutdown(nhi); } @@ -1078,6 +1112,8 @@ static const struct dev_pm_ops nhi_pm_ops = { .freeze = nhi_suspend, .poweroff = nhi_suspend, .complete = nhi_complete, + .runtime_suspend = nhi_runtime_suspend, + .runtime_resume = nhi_runtime_resume, }; static struct pci_device_id nhi_ids[] = { diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index 25758671ddf4..7442bc4c6433 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -236,8 +237,14 @@ static int tb_switch_nvm_read(void *priv, unsigned int offset, void *val, size_t bytes) { struct tb_switch *sw = priv; + int ret; + + pm_runtime_get_sync(&sw->dev); + ret = dma_port_flash_read(sw->dma_port, offset, val, bytes); + pm_runtime_mark_last_busy(&sw->dev); + pm_runtime_put_autosuspend(&sw->dev); - return dma_port_flash_read(sw->dma_port, offset, val, bytes); + return ret; } static int tb_switch_nvm_write(void *priv, unsigned int offset, void *val, @@ -722,6 +729,7 @@ static int tb_switch_set_authorized(struct tb_switch *sw, unsigned int val) * the new tunnel too early. */ pci_lock_rescan_remove(); + pm_runtime_get_sync(&sw->dev); switch (val) { /* Approve switch */ @@ -742,6 +750,8 @@ static int tb_switch_set_authorized(struct tb_switch *sw, unsigned int val) break; } + pm_runtime_mark_last_busy(&sw->dev); + pm_runtime_put_autosuspend(&sw->dev); pci_unlock_rescan_remove(); if (!ret) { @@ -888,9 +898,18 @@ static ssize_t nvm_authenticate_store(struct device *dev, nvm_clear_auth_status(sw); if (val) { + if (!sw->nvm->buf) { + ret = -EINVAL; + goto exit_unlock; + } + + pm_runtime_get_sync(&sw->dev); ret = nvm_validate_and_write(sw); - if (ret) + if (ret) { + pm_runtime_mark_last_busy(&sw->dev); + pm_runtime_put_autosuspend(&sw->dev); goto exit_unlock; + } sw->nvm->authenticating = true; @@ -898,6 +917,8 @@ static ssize_t nvm_authenticate_store(struct device *dev, ret = nvm_authenticate_host(sw); else ret = nvm_authenticate_device(sw); + pm_runtime_mark_last_busy(&sw->dev); + pm_runtime_put_autosuspend(&sw->dev); } exit_unlock: @@ -1023,9 +1044,29 @@ static void tb_switch_release(struct device *dev) kfree(sw); } +/* + * Currently only need to provide the callbacks. Everything else is handled + * in the connection manager. + */ +static int __maybe_unused tb_switch_runtime_suspend(struct device *dev) +{ + return 0; +} + +static int __maybe_unused tb_switch_runtime_resume(struct device *dev) +{ + return 0; +} + +static const struct dev_pm_ops tb_switch_pm_ops = { + SET_RUNTIME_PM_OPS(tb_switch_runtime_suspend, tb_switch_runtime_resume, + NULL) +}; + struct device_type tb_switch_type = { .name = "thunderbolt_device", .release = tb_switch_release, + .pm = &tb_switch_pm_ops, }; static int tb_switch_get_generation(struct tb_switch *sw) @@ -1365,10 +1406,21 @@ int tb_switch_add(struct tb_switch *sw) return ret; ret = tb_switch_nvm_add(sw); - if (ret) + if (ret) { device_del(&sw->dev); + return ret; + } - return ret; + pm_runtime_set_active(&sw->dev); + if (sw->rpm) { + pm_runtime_set_autosuspend_delay(&sw->dev, TB_AUTOSUSPEND_DELAY); + pm_runtime_use_autosuspend(&sw->dev); + pm_runtime_mark_last_busy(&sw->dev); + pm_runtime_enable(&sw->dev); + pm_request_autosuspend(&sw->dev); + } + + return 0; } /** @@ -1383,6 +1435,11 @@ void tb_switch_remove(struct tb_switch *sw) { int i; + if (sw->rpm) { + pm_runtime_get_sync(&sw->dev); + pm_runtime_disable(&sw->dev); + } + /* port 0 is the switch itself and never has a remote */ for (i = 1; i <= sw->config.max_port_number; i++) { if (tb_is_upstream_port(&sw->ports[i])) diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index 9d9f0ca16bfb..5067d69d0501 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -67,6 +67,7 @@ struct tb_switch_nvm { * @no_nvm_upgrade: Prevent NVM upgrade of this switch * @safe_mode: The switch is in safe-mode * @boot: Whether the switch was already authorized on boot or not + * @rpm: The switch supports runtime PM * @authorized: Whether the switch is authorized by user or policy * @work: Work used to automatically authorize a switch * @security_level: Switch supported security level @@ -101,6 +102,7 @@ struct tb_switch { bool no_nvm_upgrade; bool safe_mode; bool boot; + bool rpm; unsigned int authorized; struct work_struct work; enum tb_security_level security_level; @@ -199,6 +201,8 @@ struct tb_path { * @resume_noirq: Connection manager specific resume_noirq * @suspend: Connection manager specific suspend * @complete: Connection manager specific complete + * @runtime_suspend: Connection manager specific runtime_suspend + * @runtime_resume: Connection manager specific runtime_resume * @handle_event: Handle thunderbolt event * @get_boot_acl: Get boot ACL list * @set_boot_acl: Set boot ACL list @@ -217,6 +221,8 @@ struct tb_cm_ops { int (*resume_noirq)(struct tb *tb); int (*suspend)(struct tb *tb); void (*complete)(struct tb *tb); + int (*runtime_suspend)(struct tb *tb); + int (*runtime_resume)(struct tb *tb); void (*handle_event)(struct tb *tb, enum tb_cfg_pkg_type, const void *buf, size_t size); int (*get_boot_acl)(struct tb *tb, uuid_t *uuids, size_t nuuids); @@ -235,6 +241,8 @@ static inline void *tb_priv(struct tb *tb) return (void *)tb->privdata; } +#define TB_AUTOSUSPEND_DELAY 15000 /* ms */ + /* helper functions & macros */ /** @@ -364,6 +372,8 @@ int tb_domain_suspend_noirq(struct tb *tb); int tb_domain_resume_noirq(struct tb *tb); int tb_domain_suspend(struct tb *tb); void tb_domain_complete(struct tb *tb); +int tb_domain_runtime_suspend(struct tb *tb); +int tb_domain_runtime_resume(struct tb *tb); int tb_domain_approve_switch(struct tb *tb, struct tb_switch *sw); int tb_domain_approve_switch_key(struct tb *tb, struct tb_switch *sw); int tb_domain_challenge_switch_key(struct tb *tb, struct tb_switch *sw); diff --git a/drivers/thunderbolt/tb_msgs.h b/drivers/thunderbolt/tb_msgs.h index bc13f8d6b804..2487e162c885 100644 --- a/drivers/thunderbolt/tb_msgs.h +++ b/drivers/thunderbolt/tb_msgs.h @@ -286,6 +286,8 @@ struct icm_ar_pkg_driver_ready_response { u16 info; }; +#define ICM_AR_FLAGS_RTD3 BIT(6) + #define ICM_AR_INFO_SLEVEL_MASK GENMASK(3, 0) #define ICM_AR_INFO_BOOT_ACL_SHIFT 7 #define ICM_AR_INFO_BOOT_ACL_MASK GENMASK(11, 7) @@ -333,6 +335,8 @@ struct icm_tr_pkg_driver_ready_response { u16 reserved2; }; +#define ICM_TR_FLAGS_RTD3 BIT(6) + #define ICM_TR_INFO_SLEVEL_MASK GENMASK(2, 0) #define ICM_TR_INFO_BOOT_ACL_SHIFT 7 #define ICM_TR_INFO_BOOT_ACL_MASK GENMASK(12, 7) diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c index 8abb4e843085..db8bece63327 100644 --- a/drivers/thunderbolt/xdomain.c +++ b/drivers/thunderbolt/xdomain.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -1129,6 +1130,14 @@ struct tb_xdomain *tb_xdomain_alloc(struct tb *tb, struct device *parent, xd->dev.groups = xdomain_attr_groups; dev_set_name(&xd->dev, "%u-%llx", tb->index, route); + /* + * This keeps the DMA powered on as long as we have active + * connection to another host. + */ + pm_runtime_set_active(&xd->dev); + pm_runtime_get_noresume(&xd->dev); + pm_runtime_enable(&xd->dev); + return xd; err_free_local_uuid: @@ -1174,6 +1183,15 @@ void tb_xdomain_remove(struct tb_xdomain *xd) device_for_each_child_reverse(&xd->dev, xd, unregister_service); + /* + * Undo runtime PM here explicitly because it is possible that + * the XDomain was never added to the bus and thus device_del() + * is not called for it (device_del() would handle this otherwise). + */ + pm_runtime_disable(&xd->dev); + pm_runtime_put_noidle(&xd->dev); + pm_runtime_set_suspended(&xd->dev); + if (!device_is_registered(&xd->dev)) put_device(&xd->dev); else -- cgit v1.2.3 From 502defbb476bca222ddb5a78f2cc19c12c4e42f3 Mon Sep 17 00:00:00 2001 From: "Gustavo A. R. Silva" Date: Wed, 25 Jul 2018 08:38:32 -0500 Subject: fsi: master-ast-cf: Fix memory leak In case memory resources for *fw* were allocated, release them before return. Addresses-Coverity-ID: 1472044 ("Resource leak") Fixes: 6a794a27daca ("fsi: master-ast-cf: Add new FSI master using Aspeed ColdFire") Signed-off-by: Gustavo A. R. Silva Signed-off-by: Benjamin Herrenschmidt --- drivers/fsi/fsi-master-ast-cf.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/fsi/fsi-master-ast-cf.c b/drivers/fsi/fsi-master-ast-cf.c index b59abcf3ee32..6a36220e4b40 100644 --- a/drivers/fsi/fsi-master-ast-cf.c +++ b/drivers/fsi/fsi-master-ast-cf.c @@ -861,7 +861,8 @@ static int load_copro_firmware(struct fsi_master_acf *master) if (sig != wanted_sig) { dev_err(master->dev, "Failed to locate image sig %04x in FW blob\n", wanted_sig); - return -ENODEV; + rc = -ENODEV; + goto release_fw; } if (size > master->cf_mem_size) { dev_err(master->dev, "FW size (%zd) bigger than memory reserve (%zd)\n", @@ -870,8 +871,9 @@ static int load_copro_firmware(struct fsi_master_acf *master) } else { memcpy_toio(master->cf_mem, data, size); } - release_firmware(fw); +release_fw: + release_firmware(fw); return rc; } -- cgit v1.2.3 From 537052df223408b25bdd77dbb9bf40080f514d3c Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Thu, 26 Jul 2018 14:49:50 +1000 Subject: fsi: master-ast-cf: Rename dump_trace() to avoid name collision s390 defines a global dump_trace() symbol. Rename ours to dump_ucode_trace() to avoid a collision in build tests. Signed-off-by: Benjamin Herrenschmidt --- drivers/fsi/fsi-master-ast-cf.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/fsi/fsi-master-ast-cf.c b/drivers/fsi/fsi-master-ast-cf.c index 6a36220e4b40..04d10ea8d343 100644 --- a/drivers/fsi/fsi-master-ast-cf.c +++ b/drivers/fsi/fsi-master-ast-cf.c @@ -437,7 +437,7 @@ static int send_term(struct fsi_master_acf *master, uint8_t slave) return 0; } -static void dump_trace(struct fsi_master_acf *master) +static void dump_ucode_trace(struct fsi_master_acf *master) { char trbuf[52]; char *p; @@ -488,7 +488,7 @@ retry: } trace_fsi_master_acf_crc_rsp_error(master, crc_err_retries); if (master->trace_enabled) - dump_trace(master); + dump_ucode_trace(master); rc = clock_zeros(master, FSI_MASTER_EPOLL_CLOCKS); if (rc) { dev_warn(master->dev, @@ -525,7 +525,7 @@ retry: */ dev_dbg(master->dev, "Busy, retrying...\n"); if (master->trace_enabled) - dump_trace(master); + dump_ucode_trace(master); rc = clock_zeros(master, FSI_MASTER_DPOLL_CLOCKS); if (rc) { dev_warn(master->dev, @@ -550,13 +550,13 @@ retry: case FSI_RESP_ERRA: dev_dbg(master->dev, "ERRA received\n"); if (master->trace_enabled) - dump_trace(master); + dump_ucode_trace(master); rc = -EIO; break; case FSI_RESP_ERRC: dev_dbg(master->dev, "ERRC received\n"); if (master->trace_enabled) - dump_trace(master); + dump_ucode_trace(master); rc = -EAGAIN; break; } -- cgit v1.2.3 From 0ab5fe5374743d5a279b1ff6297ef2c54d06cd5f Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Wed, 20 Jun 2018 15:22:52 +1000 Subject: fsi: Add new central chardev support The various FSI devices (sbefifo, occ, scom, more to come) currently use misc devices. This is problematic as the minor device space for misc is limited and there can be a lot of them. Also it limits our ability to move them to a dedicated /dev/fsi directory or to be smart about device naming and numbering. It also means we have IDAs on every single of these drivers This creates a common fsi "device_type" for the optional /dev/fsi grouping and a dev_t allocator for all FSI devices. "Legacy" devices get to use a backward compatible numbering scheme (as long as chip id <16 and there's only one copy of a given unit type per chip). A single major number and a single IDA are shared for all FSI devices. This doesn't convert the FSI device drivers to use the new scheme yet, they will be converted individually. Signed-off-by: Benjamin Herrenschmidt --- drivers/fsi/Kconfig | 15 ++++++++ drivers/fsi/fsi-core.c | 95 ++++++++++++++++++++++++++++++++++++++++++++++++-- include/linux/fsi.h | 12 ++++++- 3 files changed, 119 insertions(+), 3 deletions(-) diff --git a/drivers/fsi/Kconfig b/drivers/fsi/Kconfig index 8d82b1e60514..af3a20dd5aa4 100644 --- a/drivers/fsi/Kconfig +++ b/drivers/fsi/Kconfig @@ -12,6 +12,21 @@ menuconfig FSI if FSI +config FSI_NEW_DEV_NODE + bool "Create '/dev/fsi' directory for char devices" + default n + ---help--- + This option causes char devices created for FSI devices to be + located under a common /dev/fsi/ directory. Set to N unless your + userspace has been updated to handle the new location. + + Additionally, it also causes the char device names to be offset + by one so that chip 0 will have /dev/scom1 and chip1 /dev/scom2 + to match old userspace expectations. + + New userspace will use udev rules to generate predictable access + symlinks in /dev/fsi/by-path when this option is enabled. + config FSI_MASTER_GPIO tristate "GPIO-based FSI master" depends on GPIOLIB diff --git a/drivers/fsi/fsi-core.c b/drivers/fsi/fsi-core.c index eab6c5c4990e..faa1760a5a40 100644 --- a/drivers/fsi/fsi-core.c +++ b/drivers/fsi/fsi-core.c @@ -92,6 +92,13 @@ struct fsi_slave { static const int slave_retries = 2; static int discard_errors; +static dev_t fsi_base_dev; +static DEFINE_IDA(fsi_minor_ida); +#define FSI_CHAR_MAX_DEVICES 0x1000 + +/* Legacy /dev numbering: 4 devices per chip, 16 chips */ +#define FSI_CHAR_LEGACY_TOP 64 + static int fsi_master_read(struct fsi_master *master, int link, uint8_t slave_id, uint32_t addr, void *val, size_t size); static int fsi_master_write(struct fsi_master *master, int link, @@ -627,6 +634,7 @@ static void fsi_slave_release(struct device *dev) { struct fsi_slave *slave = to_fsi_slave(dev); + fsi_free_minor(slave->dev.devt); of_node_put(dev->of_node); kfree(slave); } @@ -729,6 +737,75 @@ static ssize_t chip_id_show(struct device *dev, static DEVICE_ATTR_RO(chip_id); +static char *fsi_cdev_devnode(struct device *dev, umode_t *mode, + kuid_t *uid, kgid_t *gid) +{ +#ifdef CONFIG_FSI_NEW_DEV_NODE + return kasprintf(GFP_KERNEL, "fsi/%s", dev_name(dev)); +#else + return kasprintf(GFP_KERNEL, "%s", dev_name(dev)); +#endif +} + +const struct device_type fsi_cdev_type = { + .name = "fsi-cdev", + .devnode = fsi_cdev_devnode, +}; +EXPORT_SYMBOL_GPL(fsi_cdev_type); + +/* Backward compatible /dev/ numbering in "old style" mode */ +static int fsi_adjust_index(int index) +{ +#ifdef CONFIG_FSI_NEW_DEV_NODE + return index; +#else + return index + 1; +#endif +} + +static int __fsi_get_new_minor(struct fsi_slave *slave, enum fsi_dev_type type, + dev_t *out_dev, int *out_index) +{ + int cid = slave->chip_id; + int id; + + /* Check if we qualify for legacy numbering */ + if (cid >= 0 && cid < 16 && type < 4) { + /* Try reserving the legacy number */ + id = (cid << 4) | type; + id = ida_simple_get(&fsi_minor_ida, id, id + 1, GFP_KERNEL); + if (id >= 0) { + *out_index = fsi_adjust_index(cid); + *out_dev = fsi_base_dev + id; + return 0; + } + /* Other failure */ + if (id != -ENOSPC) + return id; + /* Fallback to non-legacy allocation */ + } + id = ida_simple_get(&fsi_minor_ida, FSI_CHAR_LEGACY_TOP, + FSI_CHAR_MAX_DEVICES, GFP_KERNEL); + if (id < 0) + return id; + *out_index = fsi_adjust_index(id); + *out_dev = fsi_base_dev + id; + return 0; +} + +int fsi_get_new_minor(struct fsi_device *fdev, enum fsi_dev_type type, + dev_t *out_dev, int *out_index) +{ + return __fsi_get_new_minor(fdev->slave, type, out_dev, out_index); +} +EXPORT_SYMBOL_GPL(fsi_get_new_minor); + +void fsi_free_minor(dev_t dev) +{ + ida_simple_remove(&fsi_minor_ida, MINOR(dev)); +} +EXPORT_SYMBOL_GPL(fsi_free_minor); + static int fsi_slave_init(struct fsi_master *master, int link, uint8_t id) { uint32_t chip_id; @@ -953,7 +1030,7 @@ static int fsi_slave_remove_device(struct device *dev, void *arg) static int fsi_master_remove_slave(struct device *dev, void *arg) { device_for_each_child(dev, NULL, fsi_slave_remove_device); - device_unregister(dev); + put_device(dev); return 0; } @@ -1091,13 +1168,27 @@ EXPORT_SYMBOL_GPL(fsi_bus_type); static int __init fsi_init(void) { - return bus_register(&fsi_bus_type); + int rc; + + rc = alloc_chrdev_region(&fsi_base_dev, 0, FSI_CHAR_MAX_DEVICES, "fsi"); + if (rc) + return rc; + rc = bus_register(&fsi_bus_type); + if (rc) + goto fail_bus; + return 0; + + fail_bus: + unregister_chrdev_region(fsi_base_dev, FSI_CHAR_MAX_DEVICES); + return rc; } postcore_initcall(fsi_init); static void fsi_exit(void) { bus_unregister(&fsi_bus_type); + unregister_chrdev_region(fsi_base_dev, FSI_CHAR_MAX_DEVICES); + ida_destroy(&fsi_minor_ida); } module_exit(fsi_exit); module_param(discard_errors, int, 0664); diff --git a/include/linux/fsi.h b/include/linux/fsi.h index 141fd38d061f..ec3be0d5b786 100644 --- a/include/linux/fsi.h +++ b/include/linux/fsi.h @@ -76,8 +76,18 @@ extern int fsi_slave_read(struct fsi_slave *slave, uint32_t addr, extern int fsi_slave_write(struct fsi_slave *slave, uint32_t addr, const void *val, size_t size); +extern struct bus_type fsi_bus_type; +extern const struct device_type fsi_cdev_type; +enum fsi_dev_type { + fsi_dev_cfam, + fsi_dev_sbefifo, + fsi_dev_scom, + fsi_dev_occ +}; -extern struct bus_type fsi_bus_type; +extern int fsi_get_new_minor(struct fsi_device *fdev, enum fsi_dev_type type, + dev_t *out_dev, int *out_index); +extern void fsi_free_minor(dev_t dev); #endif /* LINUX_FSI_H */ -- cgit v1.2.3 From 8b052dd64f998c7c8e0a0fd1d9feabc0e9bcfe42 Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Tue, 24 Jul 2018 14:39:55 +1000 Subject: fsi: sbefifo: Convert to use the new chardev This converts FSI sbefifo to use the new fsi-core controlled chardev allocator and use a real cdev instead of a miscdev. One side effect is to fix the object lifetime by removing the use of devm_kzalloc() for something that contains kobjects, and using proper reference counting. Signed-off-by: Benjamin Herrenschmidt --- drivers/fsi/fsi-sbefifo.c | 84 +++++++++++++++++++++++++++++++---------------- 1 file changed, 55 insertions(+), 29 deletions(-) diff --git a/drivers/fsi/fsi-sbefifo.c b/drivers/fsi/fsi-sbefifo.c index 33a5d9a43a07..7550497da8ef 100644 --- a/drivers/fsi/fsi-sbefifo.c +++ b/drivers/fsi/fsi-sbefifo.c @@ -17,9 +17,8 @@ #include #include #include -#include #include -#include +#include #include #include #include @@ -118,11 +117,11 @@ struct sbefifo { uint32_t magic; #define SBEFIFO_MAGIC 0x53424546 /* "SBEF" */ struct fsi_device *fsi_dev; - struct miscdevice mdev; + struct device dev; + struct cdev cdev; struct mutex lock; - char name[32]; - int idx; bool broken; + bool dead; bool async_ffdc; }; @@ -133,9 +132,9 @@ struct sbefifo_user { size_t pending_len; }; -static DEFINE_IDA(sbefifo_ida); static DEFINE_MUTEX(sbefifo_ffdc_mutex); + static void __sbefifo_dump_ffdc(struct device *dev, const __be32 *ffdc, size_t ffdc_sz, bool internal) { @@ -667,6 +666,9 @@ static int __sbefifo_submit(struct sbefifo *sbefifo, struct device *dev = &sbefifo->fsi_dev->dev; int rc; + if (sbefifo->dead) + return -ENODEV; + if (cmd_len < 2 || be32_to_cpu(command[0]) != cmd_len) { dev_vdbg(dev, "Invalid command len %zd (header: %d)\n", cmd_len, be32_to_cpu(command[0])); @@ -751,8 +753,7 @@ EXPORT_SYMBOL_GPL(sbefifo_submit); */ static int sbefifo_user_open(struct inode *inode, struct file *file) { - struct sbefifo *sbefifo = container_of(file->private_data, - struct sbefifo, mdev); + struct sbefifo *sbefifo = container_of(inode->i_cdev, struct sbefifo, cdev); struct sbefifo_user *user; user = kzalloc(sizeof(struct sbefifo_user), GFP_KERNEL); @@ -889,6 +890,14 @@ static const struct file_operations sbefifo_fops = { .release = sbefifo_user_release, }; +static void sbefifo_free(struct device *dev) +{ + struct sbefifo *sbefifo = container_of(dev, struct sbefifo, dev); + + put_device(&sbefifo->fsi_dev->dev); + kfree(sbefifo); +} + /* * Probe/remove */ @@ -900,15 +909,23 @@ static int sbefifo_probe(struct device *dev) struct device_node *np; struct platform_device *child; char child_name[32]; - int rc, child_idx = 0; + int rc, didx, child_idx = 0; dev_dbg(dev, "Found sbefifo device\n"); - sbefifo = devm_kzalloc(dev, sizeof(*sbefifo), GFP_KERNEL); + sbefifo = kzalloc(sizeof(*sbefifo), GFP_KERNEL); if (!sbefifo) return -ENOMEM; + + /* Grab a reference to the device (parent of our cdev), we'll drop it later */ + if (!get_device(dev)) { + kfree(sbefifo); + return -ENODEV; + } + sbefifo->magic = SBEFIFO_MAGIC; sbefifo->fsi_dev = fsi_dev; + dev_set_drvdata(dev, sbefifo); mutex_init(&sbefifo->lock); /* @@ -919,28 +936,30 @@ static int sbefifo_probe(struct device *dev) if (rc && rc != -ESHUTDOWN) dev_err(dev, "Initial HW cleanup failed, will retry later\n"); - sbefifo->idx = ida_simple_get(&sbefifo_ida, 1, INT_MAX, GFP_KERNEL); - snprintf(sbefifo->name, sizeof(sbefifo->name), "sbefifo%d", - sbefifo->idx); + /* Create chardev for userspace access */ + sbefifo->dev.type = &fsi_cdev_type; + sbefifo->dev.parent = dev; + sbefifo->dev.release = sbefifo_free; + device_initialize(&sbefifo->dev); - dev_set_drvdata(dev, sbefifo); + /* Allocate a minor in the FSI space */ + rc = fsi_get_new_minor(fsi_dev, fsi_dev_sbefifo, &sbefifo->dev.devt, &didx); + if (rc) + goto err; - /* Create misc chardev for userspace access */ - sbefifo->mdev.minor = MISC_DYNAMIC_MINOR; - sbefifo->mdev.fops = &sbefifo_fops; - sbefifo->mdev.name = sbefifo->name; - sbefifo->mdev.parent = dev; - rc = misc_register(&sbefifo->mdev); + dev_set_name(&sbefifo->dev, "sbefifo%d", didx); + cdev_init(&sbefifo->cdev, &sbefifo_fops); + rc = cdev_device_add(&sbefifo->cdev, &sbefifo->dev); if (rc) { - dev_err(dev, "Failed to register miscdevice: %d\n", rc); - ida_simple_remove(&sbefifo_ida, sbefifo->idx); - return rc; + dev_err(dev, "Error %d creating char device %s\n", + rc, dev_name(&sbefifo->dev)); + goto err_free_minor; } /* Create platform devs for dts child nodes (occ, etc) */ for_each_available_child_of_node(dev->of_node, np) { snprintf(child_name, sizeof(child_name), "%s-dev%d", - sbefifo->name, child_idx++); + dev_name(&sbefifo->dev), child_idx++); child = of_platform_device_create(np, child_name, dev); if (!child) dev_warn(dev, "failed to create child %s dev\n", @@ -948,6 +967,11 @@ static int sbefifo_probe(struct device *dev) } return 0; + err_free_minor: + fsi_free_minor(sbefifo->dev.devt); + err: + put_device(&sbefifo->dev); + return rc; } static int sbefifo_unregister_child(struct device *dev, void *data) @@ -967,10 +991,14 @@ static int sbefifo_remove(struct device *dev) dev_dbg(dev, "Removing sbefifo device...\n"); - misc_deregister(&sbefifo->mdev); - device_for_each_child(dev, NULL, sbefifo_unregister_child); + mutex_lock(&sbefifo->lock); + sbefifo->dead = true; + mutex_unlock(&sbefifo->lock); - ida_simple_remove(&sbefifo_ida, sbefifo->idx); + cdev_device_del(&sbefifo->cdev, &sbefifo->dev); + fsi_free_minor(sbefifo->dev.devt); + device_for_each_child(dev, NULL, sbefifo_unregister_child); + put_device(&sbefifo->dev); return 0; } @@ -1001,8 +1029,6 @@ static int sbefifo_init(void) static void sbefifo_exit(void) { fsi_driver_unregister(&sbefifo_drv); - - ida_destroy(&sbefifo_ida); } module_init(sbefifo_init); -- cgit v1.2.3 From d8f4587655f9682127351a9dc3fca61e70744294 Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Wed, 20 Jun 2018 15:33:03 +1000 Subject: fsi: scom: Convert to use the new chardev This converts FSI scom to use the new fsi-core controlled chardev allocator and use a real cdev instead of a miscdev. Signed-off-by: Benjamin Herrenschmidt --- drivers/fsi/fsi-scom.c | 130 ++++++++++++++++++++++++++++++------------------- 1 file changed, 80 insertions(+), 50 deletions(-) diff --git a/drivers/fsi/fsi-scom.c b/drivers/fsi/fsi-scom.c index 39c74351f1bf..0f303a700f69 100644 --- a/drivers/fsi/fsi-scom.c +++ b/drivers/fsi/fsi-scom.c @@ -20,9 +20,8 @@ #include #include #include -#include +#include #include -#include #include @@ -77,18 +76,12 @@ struct scom_device { struct list_head link; struct fsi_device *fsi_dev; - struct miscdevice mdev; + struct device dev; + struct cdev cdev; struct mutex lock; - char name[32]; - int idx; + bool dead; }; -#define to_scom_dev(x) container_of((x), struct scom_device, mdev) - -static struct list_head scom_devices; - -static DEFINE_IDA(scom_ida); - static int __put_scom(struct scom_device *scom_dev, uint64_t value, uint32_t addr, uint32_t *status) { @@ -374,9 +367,7 @@ static int get_scom(struct scom_device *scom, uint64_t *value, static ssize_t scom_read(struct file *filep, char __user *buf, size_t len, loff_t *offset) { - struct miscdevice *mdev = - (struct miscdevice *)filep->private_data; - struct scom_device *scom = to_scom_dev(mdev); + struct scom_device *scom = filep->private_data; struct device *dev = &scom->fsi_dev->dev; uint64_t val; int rc; @@ -385,7 +376,10 @@ static ssize_t scom_read(struct file *filep, char __user *buf, size_t len, return -EINVAL; mutex_lock(&scom->lock); - rc = get_scom(scom, &val, *offset); + if (scom->dead) + rc = -ENODEV; + else + rc = get_scom(scom, &val, *offset); mutex_unlock(&scom->lock); if (rc) { dev_dbg(dev, "get_scom fail:%d\n", rc); @@ -403,8 +397,7 @@ static ssize_t scom_write(struct file *filep, const char __user *buf, size_t len, loff_t *offset) { int rc; - struct miscdevice *mdev = filep->private_data; - struct scom_device *scom = to_scom_dev(mdev); + struct scom_device *scom = filep->private_data; struct device *dev = &scom->fsi_dev->dev; uint64_t val; @@ -418,7 +411,10 @@ static ssize_t scom_write(struct file *filep, const char __user *buf, } mutex_lock(&scom->lock); - rc = put_scom(scom, val, *offset); + if (scom->dead) + rc = -ENODEV; + else + rc = put_scom(scom, val, *offset); mutex_unlock(&scom->lock); if (rc) { dev_dbg(dev, "put_scom failed with:%d\n", rc); @@ -532,12 +528,15 @@ static int scom_check(struct scom_device *scom, void __user *argp) static long scom_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { - struct miscdevice *mdev = file->private_data; - struct scom_device *scom = to_scom_dev(mdev); + struct scom_device *scom = file->private_data; void __user *argp = (void __user *)arg; int rc = -ENOTTY; mutex_lock(&scom->lock); + if (scom->dead) { + mutex_unlock(&scom->lock); + return -ENODEV; + } switch(cmd) { case FSI_SCOM_CHECK: rc = scom_check(scom, argp); @@ -556,48 +555,88 @@ static long scom_ioctl(struct file *file, unsigned int cmd, unsigned long arg) return rc; } +static int scom_open(struct inode *inode, struct file *file) +{ + struct scom_device *scom = container_of(inode->i_cdev, struct scom_device, cdev); + + file->private_data = scom; + + return 0; +} + static const struct file_operations scom_fops = { .owner = THIS_MODULE, + .open = scom_open, .llseek = scom_llseek, .read = scom_read, .write = scom_write, .unlocked_ioctl = scom_ioctl, }; +static void scom_free(struct device *dev) +{ + struct scom_device *scom = container_of(dev, struct scom_device, dev); + + put_device(&scom->fsi_dev->dev); + kfree(scom); +} + static int scom_probe(struct device *dev) { struct fsi_device *fsi_dev = to_fsi_dev(dev); struct scom_device *scom; + int rc, didx; - scom = devm_kzalloc(dev, sizeof(*scom), GFP_KERNEL); + scom = kzalloc(sizeof(*scom), GFP_KERNEL); if (!scom) return -ENOMEM; - + dev_set_drvdata(dev, scom); mutex_init(&scom->lock); - scom->idx = ida_simple_get(&scom_ida, 1, INT_MAX, GFP_KERNEL); - snprintf(scom->name, sizeof(scom->name), "scom%d", scom->idx); - scom->fsi_dev = fsi_dev; - scom->mdev.minor = MISC_DYNAMIC_MINOR; - scom->mdev.fops = &scom_fops; - scom->mdev.name = scom->name; - scom->mdev.parent = dev; - list_add(&scom->link, &scom_devices); - - return misc_register(&scom->mdev); + + /* Grab a reference to the device (parent of our cdev), we'll drop it later */ + if (!get_device(dev)) { + kfree(scom); + return -ENODEV; + } + + /* Create chardev for userspace access */ + scom->dev.type = &fsi_cdev_type; + scom->dev.parent = dev; + scom->dev.release = scom_free; + device_initialize(&scom->dev); + + /* Allocate a minor in the FSI space */ + rc = fsi_get_new_minor(fsi_dev, fsi_dev_scom, &scom->dev.devt, &didx); + if (rc) + goto err; + + dev_set_name(&scom->dev, "scom%d", didx); + cdev_init(&scom->cdev, &scom_fops); + rc = cdev_device_add(&scom->cdev, &scom->dev); + if (rc) { + dev_err(dev, "Error %d creating char device %s\n", + rc, dev_name(&scom->dev)); + goto err_free_minor; + } + + return 0; + err_free_minor: + fsi_free_minor(scom->dev.devt); + err: + put_device(&scom->dev); + return rc; } static int scom_remove(struct device *dev) { - struct scom_device *scom, *scom_tmp; - struct fsi_device *fsi_dev = to_fsi_dev(dev); + struct scom_device *scom = dev_get_drvdata(dev); - list_for_each_entry_safe(scom, scom_tmp, &scom_devices, link) { - if (scom->fsi_dev == fsi_dev) { - list_del(&scom->link); - ida_simple_remove(&scom_ida, scom->idx); - misc_deregister(&scom->mdev); - } - } + mutex_lock(&scom->lock); + scom->dead = true; + mutex_unlock(&scom->lock); + cdev_device_del(&scom->cdev, &scom->dev); + fsi_free_minor(scom->dev.devt); + put_device(&scom->dev); return 0; } @@ -622,20 +661,11 @@ static struct fsi_driver scom_drv = { static int scom_init(void) { - INIT_LIST_HEAD(&scom_devices); return fsi_driver_register(&scom_drv); } static void scom_exit(void) { - struct list_head *pos; - struct scom_device *scom; - - list_for_each(pos, &scom_devices) { - scom = list_entry(pos, struct scom_device, link); - misc_deregister(&scom->mdev); - devm_kfree(&scom->fsi_dev->dev, scom); - } fsi_driver_unregister(&scom_drv); } -- cgit v1.2.3 From d1dcd678257603e71cf3f3d84c70e2b6f0f14bb8 Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Thu, 21 Jun 2018 12:34:22 +1000 Subject: fsi: Add cfam char devices This aims to deprecate the "raw" sysfs file used for directly accessing the CFAM and instead use a char device like the other sub drivers. Since it reworks the slave creation code and adds a cfam device type, we also use the opportunity to convert the attributes to attribute groups and add a couple more. Signed-off-by: Benjamin Herrenschmidt --- drivers/fsi/fsi-core.c | 264 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 213 insertions(+), 51 deletions(-) diff --git a/drivers/fsi/fsi-core.c b/drivers/fsi/fsi-core.c index faa1760a5a40..dea5bd48acc5 100644 --- a/drivers/fsi/fsi-core.c +++ b/drivers/fsi/fsi-core.c @@ -11,6 +11,11 @@ * 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. + * + * TODO: + * - Rework topology + * - s/chip_id/chip_loc + * - s/cfam/chip (cfam_id -> chip_id etc...) */ #include @@ -21,6 +26,9 @@ #include #include #include +#include +#include +#include #include "fsi-master.h" @@ -78,8 +86,11 @@ static DEFINE_IDA(master_ida); struct fsi_slave { struct device dev; struct fsi_master *master; - int id; - int link; + struct cdev cdev; + int cdev_idx; + int id; /* FSI address */ + int link; /* FSI link# */ + u32 cfam_id; int chip_id; uint32_t size; /* size of slave address space */ u8 t_send_delay; @@ -607,29 +618,6 @@ static const struct bin_attribute fsi_slave_raw_attr = { .write = fsi_slave_sysfs_raw_write, }; -static ssize_t fsi_slave_sysfs_term_write(struct file *file, - struct kobject *kobj, struct bin_attribute *attr, - char *buf, loff_t off, size_t count) -{ - struct fsi_slave *slave = to_fsi_slave(kobj_to_dev(kobj)); - struct fsi_master *master = slave->master; - - if (!master->term) - return -ENODEV; - - master->term(master, slave->link, slave->id); - return count; -} - -static const struct bin_attribute fsi_slave_term_attr = { - .attr = { - .name = "term", - .mode = 0200, - }, - .size = 0, - .write = fsi_slave_sysfs_term_write, -}; - static void fsi_slave_release(struct device *dev) { struct fsi_slave *slave = to_fsi_slave(dev); @@ -682,6 +670,127 @@ static struct device_node *fsi_slave_find_of_node(struct fsi_master *master, return NULL; } +static ssize_t cfam_read(struct file *filep, char __user *buf, size_t count, + loff_t *offset) +{ + struct fsi_slave *slave = filep->private_data; + size_t total_len, read_len; + loff_t off = *offset; + ssize_t rc; + + if (off < 0) + return -EINVAL; + + if (off > 0xffffffff || count > 0xffffffff || off + count > 0xffffffff) + return -EINVAL; + + for (total_len = 0; total_len < count; total_len += read_len) { + __be32 data; + + read_len = min_t(size_t, count, 4); + read_len -= off & 0x3; + + rc = fsi_slave_read(slave, off, &data, read_len); + if (rc) + goto fail; + rc = copy_to_user(buf + total_len, &data, read_len); + if (rc) { + rc = -EFAULT; + goto fail; + } + off += read_len; + } + rc = count; + fail: + *offset = off; + return count; +} + +static ssize_t cfam_write(struct file *filep, const char __user *buf, + size_t count, loff_t *offset) +{ + struct fsi_slave *slave = filep->private_data; + size_t total_len, write_len; + loff_t off = *offset; + ssize_t rc; + + + if (off < 0) + return -EINVAL; + + if (off > 0xffffffff || count > 0xffffffff || off + count > 0xffffffff) + return -EINVAL; + + for (total_len = 0; total_len < count; total_len += write_len) { + __be32 data; + + write_len = min_t(size_t, count, 4); + write_len -= off & 0x3; + + rc = copy_from_user(&data, buf + total_len, write_len); + if (rc) { + rc = -EFAULT; + goto fail; + } + rc = fsi_slave_write(slave, off, &data, write_len); + if (rc) + goto fail; + off += write_len; + } + rc = count; + fail: + *offset = off; + return count; +} + +static loff_t cfam_llseek(struct file *file, loff_t offset, int whence) +{ + switch (whence) { + case SEEK_CUR: + break; + case SEEK_SET: + file->f_pos = offset; + break; + default: + return -EINVAL; + } + + return offset; +} + +static int cfam_open(struct inode *inode, struct file *file) +{ + struct fsi_slave *slave = container_of(inode->i_cdev, struct fsi_slave, cdev); + + file->private_data = slave; + + return 0; +} + +static const struct file_operations cfam_fops = { + .owner = THIS_MODULE, + .open = cfam_open, + .llseek = cfam_llseek, + .read = cfam_read, + .write = cfam_write, +}; + +static ssize_t send_term_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fsi_slave *slave = to_fsi_slave(dev); + struct fsi_master *master = slave->master; + + if (!master->term) + return -ENODEV; + + master->term(master, slave->link, slave->id); + return count; +} + +static DEVICE_ATTR_WO(send_term); + static ssize_t slave_send_echo_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -737,6 +846,52 @@ static ssize_t chip_id_show(struct device *dev, static DEVICE_ATTR_RO(chip_id); +static ssize_t cfam_id_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct fsi_slave *slave = to_fsi_slave(dev); + + return sprintf(buf, "0x%x\n", slave->cfam_id); +} + +static DEVICE_ATTR_RO(cfam_id); + +static struct attribute *cfam_attr[] = { + &dev_attr_send_echo_delays.attr, + &dev_attr_chip_id.attr, + &dev_attr_cfam_id.attr, + &dev_attr_send_term.attr, + NULL, +}; + +static const struct attribute_group cfam_attr_group = { + .attrs = cfam_attr, +}; + +static const struct attribute_group *cfam_attr_groups[] = { + &cfam_attr_group, + NULL, +}; + +static char *cfam_devnode(struct device *dev, umode_t *mode, + kuid_t *uid, kgid_t *gid) +{ + struct fsi_slave *slave = to_fsi_slave(dev); + +#ifdef CONFIG_FSI_NEW_DEV_NODE + return kasprintf(GFP_KERNEL, "fsi/cfam%d", slave->cdev_idx); +#else + return kasprintf(GFP_KERNEL, "cfam%d", slave->cdev_idx); +#endif +} + +static const struct device_type cfam_type = { + .name = "cfam", + .devnode = cfam_devnode, + .groups = cfam_attr_groups +}; + static char *fsi_cdev_devnode(struct device *dev, umode_t *mode, kuid_t *uid, kgid_t *gid) { @@ -808,7 +963,7 @@ EXPORT_SYMBOL_GPL(fsi_free_minor); static int fsi_slave_init(struct fsi_master *master, int link, uint8_t id) { - uint32_t chip_id; + uint32_t cfam_id; struct fsi_slave *slave; uint8_t crc; __be32 data, llmode; @@ -826,17 +981,17 @@ static int fsi_slave_init(struct fsi_master *master, int link, uint8_t id) link, id, rc); return -ENODEV; } - chip_id = be32_to_cpu(data); + cfam_id = be32_to_cpu(data); - crc = crc4(0, chip_id, 32); + crc = crc4(0, cfam_id, 32); if (crc) { - dev_warn(&master->dev, "slave %02x:%02x invalid chip id CRC!\n", + dev_warn(&master->dev, "slave %02x:%02x invalid cfam id CRC!\n", link, id); return -EIO; } dev_dbg(&master->dev, "fsi: found chip %08x at %02x:%02x:%02x\n", - chip_id, master->idx, link, id); + cfam_id, master->idx, link, id); /* If we're behind a master that doesn't provide a self-running bus * clock, put the slave into async mode @@ -859,10 +1014,14 @@ static int fsi_slave_init(struct fsi_master *master, int link, uint8_t id) if (!slave) return -ENOMEM; - slave->master = master; + dev_set_name(&slave->dev, "slave@%02x:%02x", link, id); + slave->dev.type = &cfam_type; slave->dev.parent = &master->dev; slave->dev.of_node = fsi_slave_find_of_node(master, link, id); slave->dev.release = fsi_slave_release; + device_initialize(&slave->dev); + slave->cfam_id = cfam_id; + slave->master = master; slave->link = link; slave->id = id; slave->size = FSI_SLAVE_SIZE_23b; @@ -877,6 +1036,21 @@ static int fsi_slave_init(struct fsi_master *master, int link, uint8_t id) slave->chip_id = prop; } + + /* Allocate a minor in the FSI space */ + rc = __fsi_get_new_minor(slave, fsi_dev_cfam, &slave->dev.devt, + &slave->cdev_idx); + if (rc) + goto err_free; + + /* Create chardev for userspace access */ + cdev_init(&slave->cdev, &cfam_fops); + rc = cdev_device_add(&slave->cdev, &slave->dev); + if (rc) { + dev_err(&slave->dev, "Error %d creating slave device\n", rc); + goto err_free; + } + rc = fsi_slave_set_smode(slave); if (rc) { dev_warn(&master->dev, @@ -890,30 +1064,11 @@ static int fsi_slave_init(struct fsi_master *master, int link, uint8_t id) slave->t_send_delay, slave->t_echo_delay); - dev_set_name(&slave->dev, "slave@%02x:%02x", link, id); - rc = device_register(&slave->dev); - if (rc < 0) { - dev_warn(&master->dev, "failed to create slave device: %d\n", - rc); - put_device(&slave->dev); - return rc; - } - + /* Legacy raw file -> to be removed */ rc = device_create_bin_file(&slave->dev, &fsi_slave_raw_attr); if (rc) dev_warn(&slave->dev, "failed to create raw attr: %d\n", rc); - rc = device_create_bin_file(&slave->dev, &fsi_slave_term_attr); - if (rc) - dev_warn(&slave->dev, "failed to create term attr: %d\n", rc); - - rc = device_create_file(&slave->dev, &dev_attr_send_echo_delays); - if (rc) - dev_warn(&slave->dev, "failed to create delay attr: %d\n", rc); - - rc = device_create_file(&slave->dev, &dev_attr_chip_id); - if (rc) - dev_warn(&slave->dev, "failed to create chip id: %d\n", rc); rc = fsi_slave_scan(slave); if (rc) @@ -921,6 +1076,10 @@ static int fsi_slave_init(struct fsi_master *master, int link, uint8_t id) rc); return rc; + + err_free: + put_device(&slave->dev); + return rc; } /* FSI master support */ @@ -1029,7 +1188,10 @@ static int fsi_slave_remove_device(struct device *dev, void *arg) static int fsi_master_remove_slave(struct device *dev, void *arg) { + struct fsi_slave *slave = to_fsi_slave(dev); + device_for_each_child(dev, NULL, fsi_slave_remove_device); + cdev_device_del(&slave->cdev, &slave->dev); put_device(dev); return 0; } -- cgit v1.2.3 From 9840fcd8cc43bfba486a53b4461044f1a1189cdc Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Thu, 21 Jun 2018 18:00:05 +1000 Subject: fsi: Prevent multiple concurrent rescans The bus scanning process isn't terribly good at parallel attempts at rescanning the same bus. Let's have a per-master mutex protecting the scanning process. Signed-off-by: Benjamin Herrenschmidt --- drivers/fsi/fsi-core.c | 16 ++++++++++++++-- drivers/fsi/fsi-master.h | 2 ++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/drivers/fsi/fsi-core.c b/drivers/fsi/fsi-core.c index dea5bd48acc5..2c31563fdcae 100644 --- a/drivers/fsi/fsi-core.c +++ b/drivers/fsi/fsi-core.c @@ -1203,8 +1203,14 @@ static void fsi_master_unscan(struct fsi_master *master) int fsi_master_rescan(struct fsi_master *master) { + int rc; + + mutex_lock(&master->scan_lock); fsi_master_unscan(master); - return fsi_master_scan(master); + rc = fsi_master_scan(master); + mutex_unlock(&master->scan_lock); + + return rc; } EXPORT_SYMBOL_GPL(fsi_master_rescan); @@ -1240,6 +1246,7 @@ int fsi_master_register(struct fsi_master *master) int rc; struct device_node *np; + mutex_init(&master->scan_lock); master->idx = ida_simple_get(&master_ida, 0, INT_MAX, GFP_KERNEL); dev_set_name(&master->dev, "fsi%d", master->idx); @@ -1264,8 +1271,11 @@ int fsi_master_register(struct fsi_master *master) } np = dev_of_node(&master->dev); - if (!of_property_read_bool(np, "no-scan-on-init")) + if (!of_property_read_bool(np, "no-scan-on-init")) { + mutex_lock(&master->scan_lock); fsi_master_scan(master); + mutex_unlock(&master->scan_lock); + } return 0; } @@ -1278,7 +1288,9 @@ void fsi_master_unregister(struct fsi_master *master) master->idx = -1; } + mutex_lock(&master->scan_lock); fsi_master_unscan(master); + mutex_unlock(&master->scan_lock); device_unregister(&master->dev); } EXPORT_SYMBOL_GPL(fsi_master_unregister); diff --git a/drivers/fsi/fsi-master.h b/drivers/fsi/fsi-master.h index f653f75da7be..040a7d4cf717 100644 --- a/drivers/fsi/fsi-master.h +++ b/drivers/fsi/fsi-master.h @@ -18,6 +18,7 @@ #define DRIVERS_FSI_MASTER_H #include +#include /* Various protocol delays */ #define FSI_ECHO_DELAY_CLOCKS 16 /* Number clocks for echo delay */ @@ -59,6 +60,7 @@ struct fsi_master { int idx; int n_links; int flags; + struct mutex scan_lock; int (*read)(struct fsi_master *, int link, uint8_t id, uint32_t addr, void *val, size_t size); int (*write)(struct fsi_master *, int link, uint8_t id, -- cgit v1.2.3 From ddcaf3ca4c3c87b955ad6f57952250030be77791 Mon Sep 17 00:00:00 2001 From: Sunil Muthuswamy Date: Sat, 28 Jul 2018 21:58:45 +0000 Subject: Drivers: hv: vmus: Fix the check for return value from kmsg get dump buffer The code to support panic control message was checking the return was checking the return value from kmsg_dump_get_buffer as error value, which is not what the routine returns. This fixes it. Fixes: 81b18bce48af ("Drivers: HV: Send one page worth of kmsg dump over Hyper-V during panic") Signed-off-by: Sunil Muthuswamy Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman --- drivers/hv/vmbus_drv.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c index 05e37283d7c3..a7f33c1f42c5 100644 --- a/drivers/hv/vmbus_drv.c +++ b/drivers/hv/vmbus_drv.c @@ -1047,13 +1047,10 @@ static void hv_kmsg_dump(struct kmsg_dumper *dumper, * Write dump contents to the page. No need to synchronize; panic should * be single-threaded. */ - if (!kmsg_dump_get_buffer(dumper, true, hv_panic_page, - PAGE_SIZE, &bytes_written)) { - pr_err("Hyper-V: Unable to get kmsg data for panic\n"); - return; - } - - hyperv_report_panic_msg(panic_pa, bytes_written); + kmsg_dump_get_buffer(dumper, true, hv_panic_page, PAGE_SIZE, + &bytes_written); + if (bytes_written) + hyperv_report_panic_msg(panic_pa, bytes_written); } static struct kmsg_dumper hv_kmsg_dumper = { -- cgit v1.2.3 From 8afc06dd75c06b9d6e70e4a72fdafbd4cab8d1df Mon Sep 17 00:00:00 2001 From: Sunil Muthuswamy Date: Sat, 28 Jul 2018 21:58:46 +0000 Subject: Drivers: hv: vmbus: Fix the issue with freeing up hv_ctl_table_hdr The check to free the Hyper-V control table header was reversed. This fixes it. Fixes: 81b18bce48af ("Drivers: HV: Send one page worth of kmsg dump over Hyper-V during panic") Signed-off-by: Sunil Muthuswamy Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman --- drivers/hv/vmbus_drv.c | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c index a7f33c1f42c5..5e946b1be54c 100644 --- a/drivers/hv/vmbus_drv.c +++ b/drivers/hv/vmbus_drv.c @@ -1176,11 +1176,8 @@ err_alloc: bus_unregister(&hv_bus); free_page((unsigned long)hv_panic_page); - if (!hv_ctl_table_hdr) { - unregister_sysctl_table(hv_ctl_table_hdr); - hv_ctl_table_hdr = NULL; - } - + unregister_sysctl_table(hv_ctl_table_hdr); + hv_ctl_table_hdr = NULL; return ret; } @@ -1891,11 +1888,8 @@ static void __exit vmbus_exit(void) } free_page((unsigned long)hv_panic_page); - if (!hv_ctl_table_hdr) { - unregister_sysctl_table(hv_ctl_table_hdr); - hv_ctl_table_hdr = NULL; - } - + unregister_sysctl_table(hv_ctl_table_hdr); + hv_ctl_table_hdr = NULL; bus_unregister(&hv_bus); cpuhp_remove_state(hyperv_cpuhp_online); -- cgit v1.2.3 From 9d9c9656871cd9fc1d03a2b4f47a278d54c6d03d Mon Sep 17 00:00:00 2001 From: Sunil Muthuswamy Date: Sat, 28 Jul 2018 21:58:47 +0000 Subject: Drivers: hv: vmbus: Get rid of MSR access from vmbus_drv.c Get rid of ISA specific code from vmus_drv.c which is common code. Fixes: 81b18bce48af ("Drivers: HV: Send one page worth of kmsg dump over Hyper-V during panic") Signed-off-by: Sunil Muthuswamy Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman --- arch/x86/include/asm/mshyperv.h | 3 +++ drivers/hv/vmbus_drv.c | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/arch/x86/include/asm/mshyperv.h b/arch/x86/include/asm/mshyperv.h index 406da8d159e3..4d82ad347b75 100644 --- a/arch/x86/include/asm/mshyperv.h +++ b/arch/x86/include/asm/mshyperv.h @@ -97,6 +97,9 @@ static inline void vmbus_signal_eom(struct hv_message *msg, u32 old_msg_type) #define hv_set_synint_state(int_num, val) \ wrmsrl(HV_X64_MSR_SINT0 + int_num, val) +#define hv_get_crash_ctl(val) \ + rdmsrl(HV_X64_MSR_CRASH_CTL, val) + void hyperv_callback_vector(void); void hyperv_reenlightenment_vector(void); #ifdef CONFIG_TRACING diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c index 5e946b1be54c..db145e1a7049 100644 --- a/drivers/hv/vmbus_drv.c +++ b/drivers/hv/vmbus_drv.c @@ -1146,7 +1146,7 @@ static int vmbus_bus_init(void) * Register for panic kmsg callback only if the right * capability is supported by the hypervisor. */ - rdmsrl(HV_X64_MSR_CRASH_CTL, hyperv_crash_ctl); + hv_get_crash_ctl(hyperv_crash_ctl); if (hyperv_crash_ctl & HV_CRASH_CTL_CRASH_NOTIFY_MSG) { hv_panic_page = (void *)get_zeroed_page(GFP_KERNEL); if (hv_panic_page) { -- cgit v1.2.3 From 7ceb1c37533e2298797188087796dd44931d86af Mon Sep 17 00:00:00 2001 From: Stephen Hemminger Date: Sat, 28 Jul 2018 21:58:48 +0000 Subject: Drivers: hv: vmbus: add numa_node to sysfs Being able to find the numa_node for a device is useful for userspace drivers (DPDK) and also for diagnosing performance issues. This makes vmbus similar to pci. Signed-off-by: Stephen Hemminger Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/stable/sysfs-bus-vmbus | 7 +++++++ drivers/hv/vmbus_drv.c | 17 +++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/Documentation/ABI/stable/sysfs-bus-vmbus b/Documentation/ABI/stable/sysfs-bus-vmbus index 3eaffbb2d468..3fed8fdb873d 100644 --- a/Documentation/ABI/stable/sysfs-bus-vmbus +++ b/Documentation/ABI/stable/sysfs-bus-vmbus @@ -42,6 +42,13 @@ Contact: K. Y. Srinivasan Description: The 16 bit vendor ID of the device Users: tools/hv/lsvmbus and user level RDMA libraries +What: /sys/bus/vmbus/devices//numa_node +Date: Jul 2018 +KernelVersion: 4.19 +Contact: Stephen Hemminger +Description: This NUMA node to which the VMBUS device is + attached, or -1 if the node is unknown. + What: /sys/bus/vmbus/devices//channels/ Date: September. 2017 KernelVersion: 4.14 diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c index db145e1a7049..b1b548a21f91 100644 --- a/drivers/hv/vmbus_drv.c +++ b/drivers/hv/vmbus_drv.c @@ -210,6 +210,20 @@ static ssize_t modalias_show(struct device *dev, } static DEVICE_ATTR_RO(modalias); +#ifdef CONFIG_NUMA +static ssize_t numa_node_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct hv_device *hv_dev = device_to_hv_device(dev); + + if (!hv_dev->channel) + return -ENODEV; + + return sprintf(buf, "%d\n", hv_dev->channel->numa_node); +} +static DEVICE_ATTR_RO(numa_node); +#endif + static ssize_t server_monitor_pending_show(struct device *dev, struct device_attribute *dev_attr, char *buf) @@ -492,6 +506,9 @@ static struct attribute *vmbus_dev_attrs[] = { &dev_attr_class_id.attr, &dev_attr_device_id.attr, &dev_attr_modalias.attr, +#ifdef CONFIG_NUMA + &dev_attr_numa_node.attr, +#endif &dev_attr_server_monitor_pending.attr, &dev_attr_client_monitor_pending.attr, &dev_attr_server_monitor_latency.attr, -- cgit v1.2.3 From 95883676e34ab93f600787cc9831707bcdad4398 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Thu, 2 Aug 2018 10:15:58 +0300 Subject: uio: pruss: fix error handling in probe There are two bugs here. First the error codes weren't set on several paths. And second, if the call to request_threaded_irq() inside uio_register_device() fails then it would lead to a double free when we call uio_unregister_device() inside pruss_cleanup(). Signed-off-by: Dan Carpenter Signed-off-by: Greg Kroah-Hartman --- drivers/uio/uio_pruss.c | 69 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 45 insertions(+), 24 deletions(-) diff --git a/drivers/uio/uio_pruss.c b/drivers/uio/uio_pruss.c index 91aea8823af5..1cc175d3c25c 100644 --- a/drivers/uio/uio_pruss.c +++ b/drivers/uio/uio_pruss.c @@ -122,7 +122,7 @@ static int pruss_probe(struct platform_device *pdev) struct uio_pruss_dev *gdev; struct resource *regs_prussio; struct device *dev = &pdev->dev; - int ret = -ENODEV, cnt = 0, len; + int ret, cnt, i, len; struct uio_pruss_pdata *pdata = dev_get_platdata(dev); gdev = kzalloc(sizeof(struct uio_pruss_dev), GFP_KERNEL); @@ -131,8 +131,8 @@ static int pruss_probe(struct platform_device *pdev) gdev->info = kcalloc(MAX_PRUSS_EVT, sizeof(*p), GFP_KERNEL); if (!gdev->info) { - kfree(gdev); - return -ENOMEM; + ret = -ENOMEM; + goto err_free_gdev; } /* Power on PRU in case its not done as part of boot-loader */ @@ -140,29 +140,26 @@ static int pruss_probe(struct platform_device *pdev) if (IS_ERR(gdev->pruss_clk)) { dev_err(dev, "Failed to get clock\n"); ret = PTR_ERR(gdev->pruss_clk); - kfree(gdev->info); - kfree(gdev); - return ret; - } else { - ret = clk_enable(gdev->pruss_clk); - if (ret) { - dev_err(dev, "Failed to enable clock\n"); - clk_put(gdev->pruss_clk); - kfree(gdev->info); - kfree(gdev); - return ret; - } + goto err_free_info; + } + + ret = clk_enable(gdev->pruss_clk); + if (ret) { + dev_err(dev, "Failed to enable clock\n"); + goto err_clk_put; } regs_prussio = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!regs_prussio) { dev_err(dev, "No PRUSS I/O resource specified\n"); - goto out_free; + ret = -EIO; + goto err_clk_disable; } if (!regs_prussio->start) { dev_err(dev, "Invalid memory resource\n"); - goto out_free; + ret = -EIO; + goto err_clk_disable; } if (pdata->sram_pool) { @@ -172,7 +169,8 @@ static int pruss_probe(struct platform_device *pdev) sram_pool_sz, &gdev->sram_paddr); if (!gdev->sram_vaddr) { dev_err(dev, "Could not allocate SRAM pool\n"); - goto out_free; + ret = -ENOMEM; + goto err_clk_disable; } } @@ -180,14 +178,16 @@ static int pruss_probe(struct platform_device *pdev) &(gdev->ddr_paddr), GFP_KERNEL | GFP_DMA); if (!gdev->ddr_vaddr) { dev_err(dev, "Could not allocate external memory\n"); - goto out_free; + ret = -ENOMEM; + goto err_free_sram; } len = resource_size(regs_prussio); gdev->prussio_vaddr = ioremap(regs_prussio->start, len); if (!gdev->prussio_vaddr) { dev_err(dev, "Can't remap PRUSS I/O address range\n"); - goto out_free; + ret = -ENOMEM; + goto err_free_ddr_vaddr; } gdev->pintc_base = pdata->pintc_base; @@ -215,15 +215,36 @@ static int pruss_probe(struct platform_device *pdev) p->priv = gdev; ret = uio_register_device(dev, p); - if (ret < 0) - goto out_free; + if (ret < 0) { + kfree(p->name); + goto err_unloop; + } } platform_set_drvdata(pdev, gdev); return 0; -out_free: - pruss_cleanup(dev, gdev); +err_unloop: + for (i = 0, p = gdev->info; i < cnt; i++, p++) { + uio_unregister_device(p); + kfree(p->name); + } + iounmap(gdev->prussio_vaddr); +err_free_ddr_vaddr: + dma_free_coherent(dev, extram_pool_sz, gdev->ddr_vaddr, + gdev->ddr_paddr); +err_free_sram: + if (pdata->sram_pool) + gen_pool_free(gdev->sram_pool, gdev->sram_vaddr, sram_pool_sz); +err_clk_disable: + clk_disable(gdev->pruss_clk); +err_clk_put: + clk_put(gdev->pruss_clk); +err_free_info: + kfree(gdev->info); +err_free_gdev: + kfree(gdev); + return ret; } -- cgit v1.2.3 From b34e9a15b37b8ddbf06a4da142b0c39c74211eb4 Mon Sep 17 00:00:00 2001 From: Xiubo Li Date: Mon, 30 Jul 2018 03:11:48 -0400 Subject: uio: fix possible circular locking dependency The call trace: XXX/1910 is trying to acquire lock: (&mm->mmap_sem){++++++}, at: [] might_fault+0x57/0xb0 but task is already holding lock: (&idev->info_lock){+.+...}, at: [] uio_write+0x46/0x130 [uio] which lock already depends on the new lock. the existing dependency chain (in reverse order) is: -> #1 (&idev->info_lock){+.+...}: [] lock_acquire+0x99/0x1e0 [] mutex_lock_nested+0x93/0x410 [] uio_mmap+0x2d/0x170 [uio] [] mmap_region+0x428/0x650 [] do_mmap+0x3b8/0x4e0 [] vm_mmap_pgoff+0xd3/0x120 [] SyS_mmap_pgoff+0x1f1/0x270 [] SyS_mmap+0x22/0x30 [] system_call_fastpath+0x1c/0x21 -> #0 (&mm->mmap_sem){++++++}: [] __lock_acquire+0xdac/0x15f0 [] lock_acquire+0x99/0x1e0 [] might_fault+0x84/0xb0 [] uio_write+0xb4/0x130 [uio] [] vfs_write+0xc3/0x1f0 [] SyS_write+0x8a/0x100 [] system_call_fastpath+0x1c/0x21 other info that might help us debug this: Possible unsafe locking scenario: CPU0 CPU1 ---- ---- lock(&idev->info_lock); lock(&mm->mmap_sem); lock(&idev->info_lock); lock(&mm->mmap_sem); *** DEADLOCK *** 1 lock held by XXX/1910: #0: (&idev->info_lock){+.+...}, at: [] uio_write+0x46/0x130 [uio] stack backtrace: CPU: 0 PID: 1910 Comm: XXX Kdump: loaded Not tainted #1 Hardware name: VMware, Inc. VMware Virtual Platform/440BX Desktop Reference Platform, BIOS 6.00 05/19/2017 Call Trace: [] dump_stack+0x19/0x1b [] print_circular_bug+0x1f9/0x207 [] check_prevs_add+0x957/0x960 [] __lock_acquire+0xdac/0x15f0 [] ? mark_held_locks+0xb9/0x140 [] lock_acquire+0x99/0x1e0 [] ? might_fault+0x57/0xb0 [] might_fault+0x84/0xb0 [] ? might_fault+0x57/0xb0 [] uio_write+0xb4/0x130 [uio] [] vfs_write+0xc3/0x1f0 [] ? fget_light+0xfc/0x510 [] SyS_write+0x8a/0x100 [] system_call_fastpath+0x1c/0x21 Signed-off-by: Xiubo Li Signed-off-by: Greg Kroah-Hartman --- drivers/uio/uio.c | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/drivers/uio/uio.c b/drivers/uio/uio.c index 144cf7365288..3f76e38e6f30 100644 --- a/drivers/uio/uio.c +++ b/drivers/uio/uio.c @@ -624,6 +624,12 @@ static ssize_t uio_write(struct file *filep, const char __user *buf, ssize_t retval; s32 irq_on; + if (count != sizeof(s32)) + return -EINVAL; + + if (copy_from_user(&irq_on, buf, count)) + return -EFAULT; + mutex_lock(&idev->info_lock); if (!idev->info) { retval = -EINVAL; @@ -635,21 +641,11 @@ static ssize_t uio_write(struct file *filep, const char __user *buf, goto out; } - if (count != sizeof(s32)) { - retval = -EINVAL; - goto out; - } - if (!idev->info->irqcontrol) { retval = -ENOSYS; goto out; } - if (copy_from_user(&irq_on, buf, count)) { - retval = -EFAULT; - goto out; - } - retval = idev->info->irqcontrol(idev->info, irq_on); out: -- cgit v1.2.3 From 98e70866aacb1fcaa7b710fc6bca9862bf47421a Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 31 Jul 2018 09:35:33 +0300 Subject: mei: add support for variable length mei headers. Remove header size knowledge from me and txe hw layers, this requires to change the write handler to accept header and its length as well as data and its length. HBM messages are fixed to use basic header, hence we add mei_hbm2slots() that converts HBM message length and mei message header, while mei_data2slots() converts data length directly to the slots. Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/client.c | 31 ++++++++++++++++++------------- drivers/misc/mei/hbm.c | 30 ++++++++++++++++++++++-------- drivers/misc/mei/hw-me.c | 34 ++++++++++++++++++++-------------- drivers/misc/mei/hw-txe.c | 42 +++++++++++++++++++++--------------------- drivers/misc/mei/interrupt.c | 4 ++-- drivers/misc/mei/mei_dev.h | 25 +++++++++++++++++++------ 6 files changed, 102 insertions(+), 64 deletions(-) diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c index 207d2f5d5702..0a9173827461 100644 --- a/drivers/misc/mei/client.c +++ b/drivers/misc/mei/client.c @@ -863,7 +863,7 @@ int mei_cl_irq_disconnect(struct mei_cl *cl, struct mei_cl_cb *cb, int slots; int ret; - msg_slots = mei_data2slots(sizeof(struct hbm_client_connect_request)); + msg_slots = mei_hbm2slots(sizeof(struct hbm_client_connect_request)); slots = mei_hbuf_empty_slots(dev); if (slots < 0) return -EOVERFLOW; @@ -1055,11 +1055,10 @@ int mei_cl_irq_connect(struct mei_cl *cl, struct mei_cl_cb *cb, int slots; int rets; - msg_slots = mei_data2slots(sizeof(struct hbm_client_connect_request)); - if (mei_cl_is_other_connecting(cl)) return 0; + msg_slots = mei_hbm2slots(sizeof(struct hbm_client_connect_request)); slots = mei_hbuf_empty_slots(dev); if (slots < 0) return -EOVERFLOW; @@ -1299,7 +1298,7 @@ int mei_cl_irq_notify(struct mei_cl *cl, struct mei_cl_cb *cb, int ret; bool request; - msg_slots = mei_data2slots(sizeof(struct hbm_client_connect_request)); + msg_slots = mei_hbm2slots(sizeof(struct hbm_client_connect_request)); slots = mei_hbuf_empty_slots(dev); if (slots < 0) return -EOVERFLOW; @@ -1571,6 +1570,7 @@ int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb, struct mei_device *dev; struct mei_msg_data *buf; struct mei_msg_hdr mei_hdr; + size_t hdr_len = sizeof(mei_hdr); size_t len; size_t hbuf_len; int hbuf_slots; @@ -1601,7 +1601,8 @@ int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb, rets = -EOVERFLOW; goto err; } - hbuf_len = mei_slots2data(hbuf_slots) - sizeof(struct mei_msg_hdr); + + hbuf_len = mei_slots2data(hbuf_slots); mei_msg_hdr_init(&mei_hdr, cb); @@ -1609,11 +1610,11 @@ int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb, * Split the message only if we can write the whole host buffer * otherwise wait for next time the host buffer is empty. */ - if (hbuf_len >= len) { + if (len + hdr_len <= hbuf_len) { mei_hdr.length = len; mei_hdr.msg_complete = 1; } else if ((u32)hbuf_slots == mei_hbuf_depth(dev)) { - mei_hdr.length = hbuf_len; + mei_hdr.length = hbuf_len - hdr_len; } else { return 0; } @@ -1621,7 +1622,8 @@ int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb, cl_dbg(dev, cl, "buf: size = %zu idx = %zu\n", cb->buf.size, cb->buf_idx); - rets = mei_write_message(dev, &mei_hdr, buf->data + cb->buf_idx); + rets = mei_write_message(dev, &mei_hdr, hdr_len, + buf->data + cb->buf_idx, mei_hdr.length); if (rets) goto err; @@ -1661,6 +1663,7 @@ ssize_t mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb) struct mei_device *dev; struct mei_msg_data *buf; struct mei_msg_hdr mei_hdr; + size_t hdr_len = sizeof(mei_hdr); size_t len; size_t hbuf_len; int hbuf_slots; @@ -1716,15 +1719,17 @@ ssize_t mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb) goto out; } - hbuf_len = mei_slots2data(hbuf_slots) - sizeof(struct mei_msg_hdr); - if (hbuf_len >= len) { + hbuf_len = mei_slots2data(hbuf_slots); + + if (len + hdr_len <= hbuf_len) { mei_hdr.length = len; mei_hdr.msg_complete = 1; } else { - mei_hdr.length = hbuf_len; + mei_hdr.length = hbuf_len - hdr_len; } - rets = mei_write_message(dev, &mei_hdr, buf->data); + rets = mei_write_message(dev, &mei_hdr, hdr_len, + buf->data, mei_hdr.length); if (rets) goto err; @@ -1761,7 +1766,7 @@ out: } } - rets = len; + rets = buf->size; err: cl_dbg(dev, cl, "rpm: autosuspend\n"); pm_runtime_mark_last_busy(dev->dev); diff --git a/drivers/misc/mei/hbm.c b/drivers/misc/mei/hbm.c index fe6595fe94f1..8b3fd9ff6566 100644 --- a/drivers/misc/mei/hbm.c +++ b/drivers/misc/mei/hbm.c @@ -95,6 +95,20 @@ static int mei_cl_conn_status_to_errno(enum mei_cl_connect_status status) } } +/** + * mei_hbm_write_message - wrapper for sending hbm messages. + * + * @dev: mei device + * @hdr: mei header + * @data: payload + */ +static inline int mei_hbm_write_message(struct mei_device *dev, + struct mei_msg_hdr *hdr, + const void *data) +{ + return mei_write_message(dev, hdr, sizeof(*hdr), data, hdr->length); +} + /** * mei_hbm_idle - set hbm to idle state * @@ -174,7 +188,7 @@ static inline int mei_hbm_cl_write(struct mei_device *dev, struct mei_cl *cl, mei_hbm_hdr(&mei_hdr, len); mei_hbm_cl_hdr(cl, hbm_cmd, buf, len); - return mei_write_message(dev, &mei_hdr, buf); + return mei_hbm_write_message(dev, &mei_hdr, buf); } /** @@ -267,7 +281,7 @@ int mei_hbm_start_req(struct mei_device *dev) start_req.host_version.minor_version = HBM_MINOR_VERSION; dev->hbm_state = MEI_HBM_IDLE; - ret = mei_write_message(dev, &mei_hdr, &start_req); + ret = mei_hbm_write_message(dev, &mei_hdr, &start_req); if (ret) { dev_err(dev->dev, "version message write failed: ret = %d\n", ret); @@ -304,7 +318,7 @@ static int mei_hbm_enum_clients_req(struct mei_device *dev) enum_req.flags |= dev->hbm_f_ie_supported ? MEI_HBM_ENUM_F_IMMEDIATE_ENUM : 0; - ret = mei_write_message(dev, &mei_hdr, &enum_req); + ret = mei_hbm_write_message(dev, &mei_hdr, &enum_req); if (ret) { dev_err(dev->dev, "enumeration request write failed: ret = %d.\n", ret); @@ -373,7 +387,7 @@ static int mei_hbm_add_cl_resp(struct mei_device *dev, u8 addr, u8 status) resp.me_addr = addr; resp.status = status; - ret = mei_write_message(dev, &mei_hdr, &resp); + ret = mei_hbm_write_message(dev, &mei_hdr, &resp); if (ret) dev_err(dev->dev, "add client response write failed: ret = %d\n", ret); @@ -430,7 +444,7 @@ int mei_hbm_cl_notify_req(struct mei_device *dev, req.start = start; - ret = mei_write_message(dev, &mei_hdr, &req); + ret = mei_hbm_write_message(dev, &mei_hdr, &req); if (ret) dev_err(dev->dev, "notify request failed: ret = %d\n", ret); @@ -555,7 +569,7 @@ static int mei_hbm_prop_req(struct mei_device *dev, unsigned long start_idx) prop_req.hbm_cmd = HOST_CLIENT_PROPERTIES_REQ_CMD; prop_req.me_addr = addr; - ret = mei_write_message(dev, &mei_hdr, &prop_req); + ret = mei_hbm_write_message(dev, &mei_hdr, &prop_req); if (ret) { dev_err(dev->dev, "properties request write failed: ret = %d\n", ret); @@ -592,7 +606,7 @@ int mei_hbm_pg(struct mei_device *dev, u8 pg_cmd) memset(&req, 0, len); req.hbm_cmd = pg_cmd; - ret = mei_write_message(dev, &mei_hdr, &req); + ret = mei_hbm_write_message(dev, &mei_hdr, &req); if (ret) dev_err(dev->dev, "power gate command write failed.\n"); return ret; @@ -618,7 +632,7 @@ static int mei_hbm_stop_req(struct mei_device *dev) req.hbm_cmd = HOST_STOP_REQ_CMD; req.reason = DRIVER_STOP_REQUEST; - return mei_write_message(dev, &mei_hdr, &req); + return mei_hbm_write_message(dev, &mei_hdr, &req); } /** diff --git a/drivers/misc/mei/hw-me.c b/drivers/misc/mei/hw-me.c index c50671cf47eb..0e3c31595dda 100644 --- a/drivers/misc/mei/hw-me.c +++ b/drivers/misc/mei/hw-me.c @@ -517,28 +517,31 @@ static u32 mei_me_hbuf_depth(const struct mei_device *dev) return hw->hbuf_depth; } - /** * mei_me_hbuf_write - writes a message to host hw buffer. * * @dev: the device structure - * @header: mei HECI header of message - * @buf: message payload will be written + * @hdr: header of message + * @hdr_len: header length in bytes: must be multiplication of a slot (4bytes) + * @data: payload + * @data_len: payload length in bytes * - * Return: -EIO if write has failed + * Return: 0 if success, < 0 - otherwise. */ static int mei_me_hbuf_write(struct mei_device *dev, - struct mei_msg_hdr *header, - const unsigned char *buf) + const void *hdr, size_t hdr_len, + const void *data, size_t data_len) { unsigned long rem; - unsigned long length = header->length; unsigned long i; - u32 *reg_buf = (u32 *)buf; + const u32 *reg_buf; u32 dw_cnt; int empty_slots; - dev_dbg(dev->dev, MEI_HDR_FMT, MEI_HDR_PRM(header)); + if (WARN_ON(!hdr || !data || hdr_len & 0x3)) + return -EINVAL; + + dev_dbg(dev->dev, MEI_HDR_FMT, MEI_HDR_PRM((struct mei_msg_hdr *)hdr)); empty_slots = mei_hbuf_empty_slots(dev); dev_dbg(dev->dev, "empty slots = %hu.\n", empty_slots); @@ -546,20 +549,23 @@ static int mei_me_hbuf_write(struct mei_device *dev, if (empty_slots < 0) return -EOVERFLOW; - dw_cnt = mei_data2slots(length); + dw_cnt = mei_data2slots(hdr_len + data_len); if (dw_cnt > (u32)empty_slots) return -EMSGSIZE; - mei_me_hcbww_write(dev, *((u32 *) header)); + reg_buf = hdr; + for (i = 0; i < hdr_len / MEI_SLOT_SIZE; i++) + mei_me_hcbww_write(dev, reg_buf[i]); - for (i = 0; i < length / MEI_SLOT_SIZE; i++) + reg_buf = data; + for (i = 0; i < data_len / MEI_SLOT_SIZE; i++) mei_me_hcbww_write(dev, reg_buf[i]); - rem = length & 0x3; + rem = data_len & 0x3; if (rem > 0) { u32 reg = 0; - memcpy(®, &buf[length - rem], rem); + memcpy(®, (const u8 *)data + data_len - rem, rem); mei_me_hcbww_write(dev, reg); } diff --git a/drivers/misc/mei/hw-txe.c b/drivers/misc/mei/hw-txe.c index 7e2026894e9f..8449fe0367ff 100644 --- a/drivers/misc/mei/hw-txe.c +++ b/drivers/misc/mei/hw-txe.c @@ -689,37 +689,34 @@ static void mei_txe_hw_config(struct mei_device *dev) hw->aliveness, hw->readiness); } - /** * mei_txe_write - writes a message to device. * * @dev: the device structure - * @header: header of message - * @buf: message buffer will be written + * @hdr: header of message + * @hdr_len: header length in bytes - must multiplication of a slot (4bytes) + * @data: payload + * @data_len: paylead length in bytes * - * Return: 0 if success, <0 - otherwise. + * Return: 0 if success, < 0 - otherwise. */ - static int mei_txe_write(struct mei_device *dev, - struct mei_msg_hdr *header, - const unsigned char *buf) + const void *hdr, size_t hdr_len, + const void *data, size_t data_len) { struct mei_txe_hw *hw = to_txe_hw(dev); unsigned long rem; - unsigned long length; - unsigned long i; + const u32 *reg_buf; u32 slots = TXE_HBUF_DEPTH; - u32 *reg_buf = (u32 *)buf; u32 dw_cnt; + unsigned long i, j; - if (WARN_ON(!header || !buf)) + if (WARN_ON(!hdr || !data || hdr_len & 0x3)) return -EINVAL; - length = header->length; - - dev_dbg(dev->dev, MEI_HDR_FMT, MEI_HDR_PRM(header)); + dev_dbg(dev->dev, MEI_HDR_FMT, MEI_HDR_PRM((struct mei_msg_hdr *)hdr)); - dw_cnt = mei_data2slots(length); + dw_cnt = mei_data2slots(hdr_len + data_len); if (dw_cnt > slots) return -EMSGSIZE; @@ -737,17 +734,20 @@ static int mei_txe_write(struct mei_device *dev, return -EAGAIN; } - mei_txe_input_payload_write(dev, 0, *((u32 *)header)); + reg_buf = hdr; + for (i = 0; i < hdr_len / MEI_SLOT_SIZE; i++) + mei_txe_input_payload_write(dev, i, reg_buf[i]); - for (i = 0; i < length / 4; i++) - mei_txe_input_payload_write(dev, i + 1, reg_buf[i]); + reg_buf = data; + for (j = 0; j < data_len / MEI_SLOT_SIZE; j++) + mei_txe_input_payload_write(dev, i + j, reg_buf[j]); - rem = length & 0x3; + rem = data_len & 0x3; if (rem > 0) { u32 reg = 0; - memcpy(®, &buf[length - rem], rem); - mei_txe_input_payload_write(dev, i + 1, reg); + memcpy(®, (const u8 *)data + data_len - rem, rem); + mei_txe_input_payload_write(dev, i + j, reg); } /* after each write the whole buffer is consumed */ diff --git a/drivers/misc/mei/interrupt.c b/drivers/misc/mei/interrupt.c index 6217cebcad3d..5a661cbdf2ae 100644 --- a/drivers/misc/mei/interrupt.c +++ b/drivers/misc/mei/interrupt.c @@ -173,7 +173,7 @@ static int mei_cl_irq_disconnect_rsp(struct mei_cl *cl, struct mei_cl_cb *cb, int slots; int ret; - msg_slots = mei_data2slots(sizeof(struct hbm_client_connect_response)); + msg_slots = mei_hbm2slots(sizeof(struct hbm_client_connect_response)); slots = mei_hbuf_empty_slots(dev); if (slots < 0) return -EOVERFLOW; @@ -208,7 +208,7 @@ static int mei_cl_irq_read(struct mei_cl *cl, struct mei_cl_cb *cb, if (!list_empty(&cl->rd_pending)) return 0; - msg_slots = mei_data2slots(sizeof(struct hbm_flow_control)); + msg_slots = mei_hbm2slots(sizeof(struct hbm_flow_control)); slots = mei_hbuf_empty_slots(dev); if (slots < 0) return -EOVERFLOW; diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index e9f37085a628..06fb5fc67fe9 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -300,8 +300,8 @@ struct mei_hw_ops { bool (*hbuf_is_ready)(struct mei_device *dev); u32 (*hbuf_depth)(const struct mei_device *dev); int (*write)(struct mei_device *dev, - struct mei_msg_hdr *hdr, - const unsigned char *buf); + const void *hdr, size_t hdr_len, + const void *data, size_t data_len); int (*rdbuf_full_slots)(struct mei_device *dev); @@ -528,14 +528,26 @@ static inline unsigned long mei_secs_to_jiffies(unsigned long sec) } /** - * mei_data2slots - get slots - number of (dwords) from a message length - * + size of the mei header + * mei_data2slots - get slots number from a message length * * @length: size of the messages in bytes * * Return: number of slots */ static inline u32 mei_data2slots(size_t length) +{ + return DIV_ROUND_UP(length, MEI_SLOT_SIZE); +} + +/** + * mei_hbm2slots - get slots number from a hbm message length + * length + size of the mei message header + * + * @length: size of the messages in bytes + * + * Return: number of slots + */ +static inline u32 mei_hbm2slots(size_t length) { return DIV_ROUND_UP(sizeof(struct mei_msg_hdr) + length, MEI_SLOT_SIZE); } @@ -656,9 +668,10 @@ static inline u32 mei_hbuf_depth(const struct mei_device *dev) } static inline int mei_write_message(struct mei_device *dev, - struct mei_msg_hdr *hdr, const void *buf) + const void *hdr, size_t hdr_len, + const void *data, size_t data_len) { - return dev->ops->write(dev, hdr, buf); + return dev->ops->write(dev, hdr, hdr_len, data, data_len); } static inline u32 mei_read_hdr(const struct mei_device *dev) -- cgit v1.2.3 From ee7aba5aba70dd98fc05697723167ebb858e7aba Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 31 Jul 2018 09:35:34 +0300 Subject: mei: hbm: define dma ring setup protocol The protocol defines how to setup an I/O ring on top of host memory to utilize the device DMA engine for faster transport. Three memory buffers are allocated. A Host circular buffer for from the Host to Device communication. A Device circular buffer for from Device to the Host communication. And finally a Control block where the pointers for the both circular buffers are managed. Signed-off-by: Tomas Winkler Signed-off-by: Alexander Usyskin Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/hw.h | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/drivers/misc/mei/hw.h b/drivers/misc/mei/hw.h index 3b325d955fbe..76fb502e6f71 100644 --- a/drivers/misc/mei/hw.h +++ b/drivers/misc/mei/hw.h @@ -122,6 +122,9 @@ #define MEI_HBM_NOTIFY_RES_CMD 0x90 #define MEI_HBM_NOTIFICATION_CMD 0x11 +#define MEI_HBM_DMA_SETUP_REQ_CMD 0x12 +#define MEI_HBM_DMA_SETUP_RES_CMD 0x92 + /* * MEI Stop Reason * used by hbm_host_stop_request.reason @@ -449,4 +452,50 @@ struct hbm_notification { u8 reserved[1]; } __packed; +/** + * struct hbm_dma_mem_dscr - dma ring + * + * @addr_hi: the high 32bits of 64 bit address + * @addr_lo: the low 32bits of 64 bit address + * @size : size in bytes (must be power of 2) + */ +struct hbm_dma_mem_dscr { + u32 addr_hi; + u32 addr_lo; + u32 size; +} __packed; + +enum { + DMA_DSCR_HOST = 0, + DMA_DSCR_DEVICE = 1, + DMA_DSCR_CTRL = 2, + DMA_DSCR_NUM, +}; + +/** + * struct hbm_dma_setup_request - dma setup request + * + * @hbm_cmd: bus message command header + * @reserved: reserved for alignment + * @dma_dscr: dma descriptor for HOST, DEVICE, and CTRL + */ +struct hbm_dma_setup_request { + u8 hbm_cmd; + u8 reserved[3]; + struct hbm_dma_mem_dscr dma_dscr[DMA_DSCR_NUM]; +} __packed; + +/** + * struct hbm_dma_setup_response - dma setup response + * + * @hbm_cmd: bus message command header + * @status: 0 on success; otherwise DMA setup failed. + * @reserved: reserved for alignment + */ +struct hbm_dma_setup_response { + u8 hbm_cmd; + u8 status; + u8 reserved[2]; +} __packed; + #endif -- cgit v1.2.3 From 9d89ddfc624649e200aeec57912635dbe259d07b Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 31 Jul 2018 09:35:35 +0300 Subject: mei: hbm: introduce dma bit in the message header Add dma_ring bit in the mei message header for conveying that the message data itself are on the dma ring. Signed-off-by: Tomas Winkler Signed-off-by: Alexander Usyskin Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/client.c | 1 + drivers/misc/mei/hbm.c | 1 + drivers/misc/mei/hw.h | 16 ++++++++++++---- drivers/misc/mei/mei_dev.h | 4 ++-- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c index 0a9173827461..4ab6251d418e 100644 --- a/drivers/misc/mei/client.c +++ b/drivers/misc/mei/client.c @@ -1551,6 +1551,7 @@ static void mei_msg_hdr_init(struct mei_msg_hdr *mei_hdr, struct mei_cl_cb *cb) mei_hdr->length = 0; mei_hdr->reserved = 0; mei_hdr->msg_complete = 0; + mei_hdr->dma_ring = 0; mei_hdr->internal = cb->internal; } diff --git a/drivers/misc/mei/hbm.c b/drivers/misc/mei/hbm.c index 8b3fd9ff6566..b0086c4aefe4 100644 --- a/drivers/misc/mei/hbm.c +++ b/drivers/misc/mei/hbm.c @@ -145,6 +145,7 @@ static inline void mei_hbm_hdr(struct mei_msg_hdr *hdr, size_t length) hdr->me_addr = 0; hdr->length = length; hdr->msg_complete = 1; + hdr->dma_ring = 0; hdr->reserved = 0; hdr->internal = 0; } diff --git a/drivers/misc/mei/hw.h b/drivers/misc/mei/hw.h index 76fb502e6f71..5fb8844a6db5 100644 --- a/drivers/misc/mei/hw.h +++ b/drivers/misc/mei/hw.h @@ -190,19 +190,27 @@ enum mei_cl_disconnect_status { MEI_CL_DISCONN_SUCCESS = MEI_HBMS_SUCCESS }; -/* - * MEI BUS Interface Section +/** + * struct mei_msg_hdr - MEI BUS Interface Section + * + * @me_addr: device address + * @host_addr: host address + * @length: message length + * @reserved: reserved + * @dma_ring: message is on dma ring + * @internal: message is internal + * @msg_complete: last packet of the message */ struct mei_msg_hdr { u32 me_addr:8; u32 host_addr:8; u32 length:9; - u32 reserved:5; + u32 reserved:4; + u32 dma_ring:1; u32 internal:1; u32 msg_complete:1; } __packed; - struct mei_bus_message { u8 hbm_cmd; u8 data[0]; diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index 06fb5fc67fe9..975afaa38a66 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -714,10 +714,10 @@ static inline void mei_dbgfs_deregister(struct mei_device *dev) {} int mei_register(struct mei_device *dev, struct device *parent); void mei_deregister(struct mei_device *dev); -#define MEI_HDR_FMT "hdr:host=%02d me=%02d len=%d internal=%1d comp=%1d" +#define MEI_HDR_FMT "hdr:host=%02d me=%02d len=%d dma=%1d internal=%1d comp=%1d" #define MEI_HDR_PRM(hdr) \ (hdr)->host_addr, (hdr)->me_addr, \ - (hdr)->length, (hdr)->internal, (hdr)->msg_complete + (hdr)->length, (hdr)->dma_ring, (hdr)->internal, (hdr)->msg_complete ssize_t mei_fw_status2str(struct mei_fw_status *fw_sts, char *buf, size_t len); /** -- cgit v1.2.3 From c2bd9fc13d0ca1926bfd8b8e6d88f93d84a51f2a Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 31 Jul 2018 09:35:36 +0300 Subject: mei: restrict dma ring support to hbm version 2.1 Only a firmware with version 2.1 and above supports dma ring feature. Signed-off-by: Tomas Winkler Signed-off-by: Alexander Usyskin Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/debugfs.c | 2 ++ drivers/misc/mei/hbm.c | 6 ++++++ drivers/misc/mei/hw.h | 6 ++++++ drivers/misc/mei/mei_dev.h | 2 ++ 4 files changed, 16 insertions(+) diff --git a/drivers/misc/mei/debugfs.c b/drivers/misc/mei/debugfs.c index c815da91089c..7b5df8fd6c5a 100644 --- a/drivers/misc/mei/debugfs.c +++ b/drivers/misc/mei/debugfs.c @@ -183,6 +183,8 @@ static ssize_t mei_dbgfs_read_devstate(struct file *fp, char __user *ubuf, dev->hbm_f_fa_supported); pos += scnprintf(buf + pos, bufsz - pos, "\tOS: %01d\n", dev->hbm_f_os_supported); + pos += scnprintf(buf + pos, bufsz - pos, "\tDR: %01d\n", + dev->hbm_f_dr_supported); } pos += scnprintf(buf + pos, bufsz - pos, "pg: %s, %s\n", diff --git a/drivers/misc/mei/hbm.c b/drivers/misc/mei/hbm.c index b0086c4aefe4..09e233d4c0de 100644 --- a/drivers/misc/mei/hbm.c +++ b/drivers/misc/mei/hbm.c @@ -1007,6 +1007,12 @@ static void mei_hbm_config_features(struct mei_device *dev) /* OS ver message Support */ if (dev->version.major_version >= HBM_MAJOR_VERSION_OS) dev->hbm_f_os_supported = 1; + + /* DMA Ring Support */ + if (dev->version.major_version > HBM_MAJOR_VERSION_DR || + (dev->version.major_version == HBM_MAJOR_VERSION_DR && + dev->version.minor_version >= HBM_MINOR_VERSION_DR)) + dev->hbm_f_dr_supported = 1; } /** diff --git a/drivers/misc/mei/hw.h b/drivers/misc/mei/hw.h index 5fb8844a6db5..65655925791a 100644 --- a/drivers/misc/mei/hw.h +++ b/drivers/misc/mei/hw.h @@ -80,6 +80,12 @@ #define HBM_MINOR_VERSION_OS 0 #define HBM_MAJOR_VERSION_OS 2 +/* + * MEI version with dma ring support + */ +#define HBM_MINOR_VERSION_DR 1 +#define HBM_MAJOR_VERSION_DR 2 + /* Host bus message command opcode */ #define MEI_HBM_CMD_OP_MSK 0x7f /* Host bus message command RESPONSE */ diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index 975afaa38a66..377397e1b5a5 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -418,6 +418,7 @@ struct mei_fw_version { * @hbm_f_fa_supported : hbm feature fixed address client * @hbm_f_ie_supported : hbm feature immediate reply to enum request * @hbm_f_os_supported : hbm feature support OS ver message + * @hbm_f_dr_supported : hbm feature dma ring supported * * @fw_ver : FW versions * @@ -495,6 +496,7 @@ struct mei_device { unsigned int hbm_f_fa_supported:1; unsigned int hbm_f_ie_supported:1; unsigned int hbm_f_os_supported:1; + unsigned int hbm_f_dr_supported:1; struct mei_fw_version fw_ver[MEI_MAX_FW_VER_BLOCKS]; -- cgit v1.2.3 From 7026a5fd7f2cd36fee5ec40f3652f942700671b7 Mon Sep 17 00:00:00 2001 From: Alexander Usyskin Date: Tue, 31 Jul 2018 09:35:37 +0300 Subject: mei: define dma ring buffer sizes for PCH12 HW and newer Define dma ring buffer sizes for PCH12 (CLN HW and newer) Signed-off-by: Alexander Usyskin Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/hw-me.c | 13 +++++++++++++ drivers/misc/mei/hw-me.h | 4 ++++ 2 files changed, 17 insertions(+) diff --git a/drivers/misc/mei/hw-me.c b/drivers/misc/mei/hw-me.c index 0e3c31595dda..0759c3a668de 100644 --- a/drivers/misc/mei/hw-me.c +++ b/drivers/misc/mei/hw-me.c @@ -19,6 +19,7 @@ #include #include #include +#include #include "mei_dev.h" #include "hbm.h" @@ -1389,6 +1390,11 @@ static bool mei_me_fw_type_sps(struct pci_dev *pdev) .fw_status.status[4] = PCI_CFG_HFS_5, \ .fw_status.status[5] = PCI_CFG_HFS_6 +#define MEI_CFG_DMA_128 \ + .dma_size[DMA_DSCR_HOST] = SZ_128K, \ + .dma_size[DMA_DSCR_DEVICE] = SZ_128K, \ + .dma_size[DMA_DSCR_CTRL] = PAGE_SIZE + /* ICH Legacy devices */ static const struct mei_cfg mei_me_ich_cfg = { MEI_CFG_ICH_HFS, @@ -1421,6 +1427,12 @@ static const struct mei_cfg mei_me_pch8_sps_cfg = { MEI_CFG_FW_SPS, }; +/* Cannon Lake and newer devices */ +static const struct mei_cfg mei_me_pch12_cfg = { + MEI_CFG_PCH8_HFS, + MEI_CFG_DMA_128, +}; + /* * mei_cfg_list - A list of platform platform specific configurations. * Note: has to be synchronized with enum mei_cfg_idx. @@ -1433,6 +1445,7 @@ static const struct mei_cfg *const mei_cfg_list[] = { [MEI_ME_PCH_CPT_PBG_CFG] = &mei_me_pch_cpt_pbg_cfg, [MEI_ME_PCH8_CFG] = &mei_me_pch8_cfg, [MEI_ME_PCH8_SPS_CFG] = &mei_me_pch8_sps_cfg, + [MEI_ME_PCH12_CFG] = &mei_me_pch12_cfg, }; const struct mei_cfg *mei_me_get_cfg(kernel_ulong_t idx) diff --git a/drivers/misc/mei/hw-me.h b/drivers/misc/mei/hw-me.h index 0c6fe71d1212..bbcc5fc106cd 100644 --- a/drivers/misc/mei/hw-me.h +++ b/drivers/misc/mei/hw-me.h @@ -31,10 +31,12 @@ * * @fw_status: FW status * @quirk_probe: device exclusion quirk + * @dma_size: device DMA buffers size */ struct mei_cfg { const struct mei_fw_status fw_status; bool (*quirk_probe)(struct pci_dev *pdev); + size_t dma_size[DMA_DSCR_NUM]; }; @@ -80,6 +82,7 @@ struct mei_me_hw { * @MEI_ME_PCH8_SPS_CFG: Platform Controller Hub Gen8 and newer * servers platforms with quirk for * SPS firmware exclusion. + * @MEI_ME_PCH12_CFG: Platform Controller Hub Gen12 and newer * @MEI_ME_NUM_CFG: Upper Sentinel. */ enum mei_cfg_idx { @@ -90,6 +93,7 @@ enum mei_cfg_idx { MEI_ME_PCH_CPT_PBG_CFG, MEI_ME_PCH8_CFG, MEI_ME_PCH8_SPS_CFG, + MEI_ME_PCH12_CFG, MEI_ME_NUM_CFG, }; -- cgit v1.2.3 From d3b26dd7cb0e3433bfd3c1d4dcf74c6039bb49fb Mon Sep 17 00:00:00 2001 From: Dexuan Cui Date: Thu, 2 Aug 2018 03:08:23 +0000 Subject: Drivers: hv: vmbus: Reset the channel callback in vmbus_onoffer_rescind() Before setting channel->rescind in vmbus_rescind_cleanup(), we should make sure the channel callback won't run any more, otherwise a high-level driver like pci_hyperv, which may be infinitely waiting for the host VSP's response and notices the channel has been rescinded, can't safely give up: e.g., in hv_pci_protocol_negotiation() -> wait_for_response(), it's unsafe to exit from wait_for_response() and proceed with the on-stack variable "comp_pkt" popped. The issue was originally spotted by Michael Kelley . In vmbus_close_internal(), the patch also minimizes the range protected by disabling/enabling channel->callback_event: we don't really need that for the whole function. Signed-off-by: Dexuan Cui Reviewed-by: Michael Kelley Cc: stable@vger.kernel.org Cc: K. Y. Srinivasan Cc: Stephen Hemminger Cc: Michael Kelley Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman --- drivers/hv/channel.c | 40 ++++++++++++++++++++++++---------------- drivers/hv/channel_mgmt.c | 6 ++++++ include/linux/hyperv.h | 2 ++ 3 files changed, 32 insertions(+), 16 deletions(-) diff --git a/drivers/hv/channel.c b/drivers/hv/channel.c index ba0a092ae085..c3949220b770 100644 --- a/drivers/hv/channel.c +++ b/drivers/hv/channel.c @@ -558,11 +558,8 @@ static void reset_channel_cb(void *arg) channel->onchannel_callback = NULL; } -static int vmbus_close_internal(struct vmbus_channel *channel) +void vmbus_reset_channel_cb(struct vmbus_channel *channel) { - struct vmbus_channel_close_channel *msg; - int ret; - /* * vmbus_on_event(), running in the per-channel tasklet, can race * with vmbus_close_internal() in the case of SMP guest, e.g., when @@ -572,6 +569,29 @@ static int vmbus_close_internal(struct vmbus_channel *channel) */ tasklet_disable(&channel->callback_event); + channel->sc_creation_callback = NULL; + + /* Stop the callback asap */ + if (channel->target_cpu != get_cpu()) { + put_cpu(); + smp_call_function_single(channel->target_cpu, reset_channel_cb, + channel, true); + } else { + reset_channel_cb(channel); + put_cpu(); + } + + /* Re-enable tasklet for use on re-open */ + tasklet_enable(&channel->callback_event); +} + +static int vmbus_close_internal(struct vmbus_channel *channel) +{ + struct vmbus_channel_close_channel *msg; + int ret; + + vmbus_reset_channel_cb(channel); + /* * In case a device driver's probe() fails (e.g., * util_probe() -> vmbus_open() returns -ENOMEM) and the device is @@ -585,16 +605,6 @@ static int vmbus_close_internal(struct vmbus_channel *channel) } channel->state = CHANNEL_OPEN_STATE; - channel->sc_creation_callback = NULL; - /* Stop callback and cancel the timer asap */ - if (channel->target_cpu != get_cpu()) { - put_cpu(); - smp_call_function_single(channel->target_cpu, reset_channel_cb, - channel, true); - } else { - reset_channel_cb(channel); - put_cpu(); - } /* Send a closing message */ @@ -639,8 +649,6 @@ static int vmbus_close_internal(struct vmbus_channel *channel) get_order(channel->ringbuffer_pagecount * PAGE_SIZE)); out: - /* re-enable tasklet for use on re-open */ - tasklet_enable(&channel->callback_event); return ret; } diff --git a/drivers/hv/channel_mgmt.c b/drivers/hv/channel_mgmt.c index f3b551a50653..0f0e091c117c 100644 --- a/drivers/hv/channel_mgmt.c +++ b/drivers/hv/channel_mgmt.c @@ -892,6 +892,12 @@ static void vmbus_onoffer_rescind(struct vmbus_channel_message_header *hdr) return; } + /* + * Before setting channel->rescind in vmbus_rescind_cleanup(), we + * should make sure the channel callback is not running any more. + */ + vmbus_reset_channel_cb(channel); + /* * Now wait for offer handling to complete. */ diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index 2330f08062c7..efda23cf32c7 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -1061,6 +1061,8 @@ extern int vmbus_establish_gpadl(struct vmbus_channel *channel, extern int vmbus_teardown_gpadl(struct vmbus_channel *channel, u32 gpadl_handle); +void vmbus_reset_channel_cb(struct vmbus_channel *channel); + extern int vmbus_recvpacket(struct vmbus_channel *channel, void *buffer, u32 bufferlen, -- cgit v1.2.3 From 6ba34171bcbd10321c6cf554e0c1144d170f9d1a Mon Sep 17 00:00:00 2001 From: Michael Kelley Date: Thu, 2 Aug 2018 03:08:24 +0000 Subject: Drivers: hv: vmbus: Remove use of slow_virt_to_phys() slow_virt_to_phys() is only implemented for arch/x86. Remove its use in arch independent Hyper-V drivers, and replace with test for vmalloc() address followed by appropriate v-to-p function. This follows the typical pattern of other drivers and avoids the need to implement slow_virt_to_phys() for Hyper-V on ARM64. Signed-off-by: Michael Kelley Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman --- drivers/hv/channel.c | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/drivers/hv/channel.c b/drivers/hv/channel.c index c3949220b770..741857d80da1 100644 --- a/drivers/hv/channel.c +++ b/drivers/hv/channel.c @@ -29,12 +29,26 @@ #include #include #include +#include #include "hyperv_vmbus.h" #define NUM_PAGES_SPANNED(addr, len) \ ((PAGE_ALIGN(addr + len) >> PAGE_SHIFT) - (addr >> PAGE_SHIFT)) +static unsigned long virt_to_hvpfn(void *addr) +{ + unsigned long paddr; + + if (is_vmalloc_addr(addr)) + paddr = page_to_phys(vmalloc_to_page(addr)) + + offset_in_page(addr); + else + paddr = __pa(addr); + + return paddr >> PAGE_SHIFT; +} + /* * vmbus_setevent- Trigger an event notification on the specified * channel. @@ -298,8 +312,8 @@ static int create_gpadl_header(void *kbuffer, u32 size, gpadl_header->range[0].byte_offset = 0; gpadl_header->range[0].byte_count = size; for (i = 0; i < pfncount; i++) - gpadl_header->range[0].pfn_array[i] = slow_virt_to_phys( - kbuffer + PAGE_SIZE * i) >> PAGE_SHIFT; + gpadl_header->range[0].pfn_array[i] = virt_to_hvpfn( + kbuffer + PAGE_SIZE * i); *msginfo = msgheader; pfnsum = pfncount; @@ -350,9 +364,8 @@ static int create_gpadl_header(void *kbuffer, u32 size, * so the hypervisor guarantees that this is ok. */ for (i = 0; i < pfncurr; i++) - gpadl_body->pfn[i] = slow_virt_to_phys( - kbuffer + PAGE_SIZE * (pfnsum + i)) >> - PAGE_SHIFT; + gpadl_body->pfn[i] = virt_to_hvpfn( + kbuffer + PAGE_SIZE * (pfnsum + i)); /* add to msg header */ list_add_tail(&msgbody->msglistentry, @@ -380,8 +393,8 @@ static int create_gpadl_header(void *kbuffer, u32 size, gpadl_header->range[0].byte_offset = 0; gpadl_header->range[0].byte_count = size; for (i = 0; i < pagecount; i++) - gpadl_header->range[0].pfn_array[i] = slow_virt_to_phys( - kbuffer + PAGE_SIZE * i) >> PAGE_SHIFT; + gpadl_header->range[0].pfn_array[i] = virt_to_hvpfn( + kbuffer + PAGE_SIZE * i); *msginfo = msgheader; } -- cgit v1.2.3 From 572086325ce9a9e348b8748e830653f3959e88b6 Mon Sep 17 00:00:00 2001 From: Michael Kelley Date: Thu, 2 Aug 2018 03:08:25 +0000 Subject: Drivers: hv: vmbus: Cleanup synic memory free path clk_evt memory is not being freed when the synic is shutdown or when there is an allocation error. Add the appropriate kfree() call, along with a comment to clarify how the memory gets freed after an allocation error. Make the free path consistent by removing checks for NULL since kfree() and free_page() already do the check. Signed-off-by: Michael Kelley Reported-by: Dan Carpenter Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman --- drivers/hv/hv.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/drivers/hv/hv.c b/drivers/hv/hv.c index 312fe5ed7c40..748a1c4172a6 100644 --- a/drivers/hv/hv.c +++ b/drivers/hv/hv.c @@ -242,6 +242,10 @@ int hv_synic_alloc(void) return 0; err: + /* + * Any memory allocations that succeeded will be freed when + * the caller cleans up by calling hv_synic_free() + */ return -ENOMEM; } @@ -254,12 +258,10 @@ void hv_synic_free(void) struct hv_per_cpu_context *hv_cpu = per_cpu_ptr(hv_context.cpu_context, cpu); - if (hv_cpu->synic_event_page) - free_page((unsigned long)hv_cpu->synic_event_page); - if (hv_cpu->synic_message_page) - free_page((unsigned long)hv_cpu->synic_message_page); - if (hv_cpu->post_msg_page) - free_page((unsigned long)hv_cpu->post_msg_page); + kfree(hv_cpu->clk_evt); + free_page((unsigned long)hv_cpu->synic_event_page); + free_page((unsigned long)hv_cpu->synic_message_page); + free_page((unsigned long)hv_cpu->post_msg_page); } kfree(hv_context.hv_numa_map); -- cgit v1.2.3 From 94aea0c6f7bdb5d224b1f48ab0e93f40bf447a4c Mon Sep 17 00:00:00 2001 From: Mircea Caprioru Date: Wed, 1 Aug 2018 10:37:39 +0200 Subject: dt-bindings: mux: add adi,adgs1408 Adding documentation for adgs1408/1409 multiplexer. The bindings follow the standard SPI and mux bindings and do not require any additional custom properties. Signed-off-by: Mircea Caprioru Reviewed-by: Rob Herring [peda: reword idle-state to non-array for singular mux controller] Signed-off-by: Peter Rosin Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/mux/adi,adgs1408.txt | 48 ++++++++++++++++++++++ MAINTAINERS | 6 +++ 2 files changed, 54 insertions(+) create mode 100644 Documentation/devicetree/bindings/mux/adi,adgs1408.txt diff --git a/Documentation/devicetree/bindings/mux/adi,adgs1408.txt b/Documentation/devicetree/bindings/mux/adi,adgs1408.txt new file mode 100644 index 000000000000..be6947f4d86b --- /dev/null +++ b/Documentation/devicetree/bindings/mux/adi,adgs1408.txt @@ -0,0 +1,48 @@ +Bindings for Analog Devices ADGS1408/1409 8:1/Dual 4:1 Mux + +Required properties: +- compatible : Should be one of + * "adi,adgs1408" + * "adi,adgs1409" +* Standard mux-controller bindings as described in mux-controller.txt + +Optional properties for ADGS1408/1409: +- gpio-controller : if present, #gpio-cells is required. +- #gpio-cells : should be <2> + - First cell is the GPO line number, i.e. 0 to 3 + for ADGS1408 and 0 to 4 for ADGS1409 + - Second cell is used to specify active high (0) + or active low (1) + +Optional properties: +- idle-state : if present, the state that the mux controller will have + when idle. The special state MUX_IDLE_AS_IS is the default and + MUX_IDLE_DISCONNECT is also supported. + +States 0 through 7 correspond to signals S1 through S8 in the datasheet. +For ADGS1409 only states 0 to 3 are available. + +Example: + + /* + * One mux controller. + * Mux state set to idle as is (no idle-state declared) + */ + &spi0 { + mux: mux-controller@0 { + compatible = "adi,adgs1408"; + reg = <0>; + spi-max-frequency = <1000000>; + #mux-control-cells = <0>; + }; + } + + adc-mux { + compatible = "io-channel-mux"; + io-channels = <&adc 1>; + io-channel-names = "parent"; + mux-controls = <&mux>; + + channels = "out_a0", "out_a1", "test0", "test1", + "out_b0", "out_b1", "testb0", "testb1"; + }; diff --git a/MAINTAINERS b/MAINTAINERS index f53d58a3b47e..3464b94bf56a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -810,6 +810,12 @@ L: linux-media@vger.kernel.org S: Maintained F: drivers/media/i2c/ad9389b* +ANALOG DEVICES INC ADGS1408 DRIVER +M: Mircea Caprioru +S: Supported +F: drivers/mux/adgs1408.c +F: Documentation/devicetree/bindings/mux/adgs1408.txt + ANALOG DEVICES INC ADV7180 DRIVER M: Lars-Peter Clausen L: linux-media@vger.kernel.org -- cgit v1.2.3 From 8b9ce6954c05e3e4115f54444c7eaf2aa2dd5e65 Mon Sep 17 00:00:00 2001 From: Mircea Caprioru Date: Wed, 1 Aug 2018 10:37:40 +0200 Subject: mux: adgs1408: new driver for Analog Devices ADGS1408/1409 mux This patch adds basic support for Analog Device ADGS1408/09 SPI mux controller. The device is probed and set to a disabled state. It uses the new mux controller framework. Signed-off-by: Mircea Caprioru Signed-off-by: Peter Rosin Signed-off-by: Greg Kroah-Hartman --- drivers/mux/Kconfig | 10 ++++ drivers/mux/Makefile | 2 + drivers/mux/adgs1408.c | 131 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 143 insertions(+) create mode 100644 drivers/mux/adgs1408.c diff --git a/drivers/mux/Kconfig b/drivers/mux/Kconfig index 6241678e99af..7659d6c5f718 100644 --- a/drivers/mux/Kconfig +++ b/drivers/mux/Kconfig @@ -21,6 +21,16 @@ config MUX_ADG792A To compile the driver as a module, choose M here: the module will be called mux-adg792a. +config MUX_ADGS1408 + tristate "Analog Devices ADGS1408/ADGS1409 Multiplexers" + depends on SPI + help + ADGS1408 8:1 multiplexer and ADGS1409 double 4:1 multiplexer + switches. + + To compile the driver as a module, choose M here: the module will + be called mux-adgs1408. + config MUX_GPIO tristate "GPIO-controlled Multiplexer" depends on GPIOLIB || COMPILE_TEST diff --git a/drivers/mux/Makefile b/drivers/mux/Makefile index c3d883955fd5..6e9fa47daf56 100644 --- a/drivers/mux/Makefile +++ b/drivers/mux/Makefile @@ -5,10 +5,12 @@ mux-core-objs := core.o mux-adg792a-objs := adg792a.o +mux-adgs1408-objs := adgs1408.o mux-gpio-objs := gpio.o mux-mmio-objs := mmio.o obj-$(CONFIG_MULTIPLEXER) += mux-core.o obj-$(CONFIG_MUX_ADG792A) += mux-adg792a.o +obj-$(CONFIG_MUX_ADGS1408) += mux-adgs1408.o obj-$(CONFIG_MUX_GPIO) += mux-gpio.o obj-$(CONFIG_MUX_MMIO) += mux-mmio.o diff --git a/drivers/mux/adgs1408.c b/drivers/mux/adgs1408.c new file mode 100644 index 000000000000..0f7cf54e3234 --- /dev/null +++ b/drivers/mux/adgs1408.c @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ADGS1408/ADGS1409 SPI MUX driver + * + * Copyright 2018 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include + +#define ADGS1408_SW_DATA (0x01) +#define ADGS1408_REG_READ(reg) ((reg) | 0x80) +#define ADGS1408_DISABLE (0x00) +#define ADGS1408_MUX(state) (((state) << 1) | 1) + +enum adgs1408_chip_id { + ADGS1408 = 1, + ADGS1409, +}; + +static int adgs1408_spi_reg_write(struct spi_device *spi, + u8 reg_addr, u8 reg_data) +{ + u8 tx_buf[2]; + + tx_buf[0] = reg_addr; + tx_buf[1] = reg_data; + + return spi_write_then_read(spi, tx_buf, sizeof(tx_buf), NULL, 0); +} + +static int adgs1408_set(struct mux_control *mux, int state) +{ + struct spi_device *spi = to_spi_device(mux->chip->dev.parent); + u8 reg; + + if (state == MUX_IDLE_DISCONNECT) + reg = ADGS1408_DISABLE; + else + reg = ADGS1408_MUX(state); + + return adgs1408_spi_reg_write(spi, ADGS1408_SW_DATA, reg); +} + +static const struct mux_control_ops adgs1408_ops = { + .set = adgs1408_set, +}; + +static int adgs1408_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + enum adgs1408_chip_id chip_id; + struct mux_chip *mux_chip; + struct mux_control *mux; + s32 idle_state; + int ret; + + chip_id = (enum adgs1408_chip_id)of_device_get_match_data(dev); + if (!chip_id) + chip_id = spi_get_device_id(spi)->driver_data; + + mux_chip = devm_mux_chip_alloc(dev, 1, 0); + if (IS_ERR(mux_chip)) + return PTR_ERR(mux_chip); + + mux_chip->ops = &adgs1408_ops; + + ret = adgs1408_spi_reg_write(spi, ADGS1408_SW_DATA, ADGS1408_DISABLE); + if (ret < 0) + return ret; + + ret = device_property_read_u32(dev, "idle-state", (u32 *)&idle_state); + if (ret < 0) + idle_state = MUX_IDLE_AS_IS; + + mux = mux_chip->mux; + + if (chip_id == ADGS1408) + mux->states = 8; + else + mux->states = 4; + + switch (idle_state) { + case MUX_IDLE_DISCONNECT: + case MUX_IDLE_AS_IS: + case 0 ... 7: + /* adgs1409 supports only 4 states */ + if (idle_state < mux->states) { + mux->idle_state = idle_state; + break; + } + /* fall through */ + default: + dev_err(dev, "invalid idle-state %d\n", idle_state); + return -EINVAL; + } + + return devm_mux_chip_register(dev, mux_chip); +} + +static const struct spi_device_id adgs1408_spi_id[] = { + { "adgs1408", ADGS1408 }, + { "adgs1409", ADGS1409 }, + { } +}; +MODULE_DEVICE_TABLE(spi, adgs1408_spi_id); + +static const struct of_device_id adgs1408_of_match[] = { + { .compatible = "adi,adgs1408", .data = (void *)ADGS1408, }, + { .compatible = "adi,adgs1409", .data = (void *)ADGS1409, }, + { } +}; +MODULE_DEVICE_TABLE(of, adgs1408_of_match); + +static struct spi_driver adgs1408_driver = { + .driver = { + .name = "adgs1408", + .of_match_table = of_match_ptr(adgs1408_of_match), + }, + .probe = adgs1408_probe, + .id_table = adgs1408_spi_id, +}; +module_spi_driver(adgs1408_driver); + +MODULE_AUTHOR("Mircea Caprioru "); +MODULE_DESCRIPTION("Analog Devices ADGS1408 MUX driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From 72755eed6c1c5312425ad3d78bf0b916769e6125 Mon Sep 17 00:00:00 2001 From: Roman Kiryanov Date: Mon, 23 Jul 2018 15:47:26 -0700 Subject: goldfish: Add missing includes to goldfish.h goldfish.h refers to external symbols such as dma_addr_t and writel. This causes compilation errors if this file is included before other header files. The mentioned symbols are defined in types.h (dma_addr_t) and io.h (writel). Signed-off-by: Roman Kiryanov Signed-off-by: Greg Kroah-Hartman --- include/linux/goldfish.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/linux/goldfish.h b/include/linux/goldfish.h index 2835c150c3ff..159b4191f15d 100644 --- a/include/linux/goldfish.h +++ b/include/linux/goldfish.h @@ -2,6 +2,9 @@ #ifndef __LINUX_GOLDFISH_H #define __LINUX_GOLDFISH_H +#include +#include + /* Helpers for Goldfish virtual platform */ static inline void gf_write_ptr(const void *ptr, void __iomem *portl, -- cgit v1.2.3 From 68275680ea12e9f378e51ff525dacbb76f006764 Mon Sep 17 00:00:00 2001 From: Roman Kiryanov Date: Mon, 23 Jul 2018 15:47:27 -0700 Subject: goldfish: Use dedicated macros instead of manual bit shifting There are dedicated macros (lower_32_bits and upper_32_bits) available to extract the lower and upper 32 bits. They provide better readability and could prevent some compilation warnings. Signed-off-by: Roman Kiryanov Signed-off-by: Greg Kroah-Hartman --- include/linux/goldfish.h | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/include/linux/goldfish.h b/include/linux/goldfish.h index 159b4191f15d..265a099cd3b8 100644 --- a/include/linux/goldfish.h +++ b/include/linux/goldfish.h @@ -2,6 +2,7 @@ #ifndef __LINUX_GOLDFISH_H #define __LINUX_GOLDFISH_H +#include #include #include @@ -10,9 +11,11 @@ static inline void gf_write_ptr(const void *ptr, void __iomem *portl, void __iomem *porth) { - writel((u32)(unsigned long)ptr, portl); + const unsigned long addr = (unsigned long)ptr; + + writel(lower_32_bits(addr), portl); #ifdef CONFIG_64BIT - writel((unsigned long)ptr >> 32, porth); + writel(upper_32_bits(addr), porth); #endif } @@ -20,9 +23,9 @@ static inline void gf_write_dma_addr(const dma_addr_t addr, void __iomem *portl, void __iomem *porth) { - writel((u32)addr, portl); + writel(lower_32_bits(addr), portl); #ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT - writel(addr >> 32, porth); + writel(upper_32_bits(addr), porth); #endif } -- cgit v1.2.3 From 9a481bdc16294414fa4d666ab4f4019224d880a5 Mon Sep 17 00:00:00 2001 From: Roman Kiryanov Date: Tue, 31 Jul 2018 17:38:50 -0700 Subject: platform: goldfish: Retire pdev_bus Not used by goldfish. Signed-off-by: Roman Kiryanov Signed-off-by: Greg Kroah-Hartman --- drivers/platform/goldfish/Kconfig | 5 - drivers/platform/goldfish/Makefile | 1 - drivers/platform/goldfish/pdev_bus.c | 232 ----------------------------------- 3 files changed, 238 deletions(-) delete mode 100644 drivers/platform/goldfish/pdev_bus.c diff --git a/drivers/platform/goldfish/Kconfig b/drivers/platform/goldfish/Kconfig index fefbb8370da0..479031aa4f88 100644 --- a/drivers/platform/goldfish/Kconfig +++ b/drivers/platform/goldfish/Kconfig @@ -10,11 +10,6 @@ menuconfig GOLDFISH if GOLDFISH -config GOLDFISH_BUS - bool "Goldfish platform bus" - ---help--- - This is a virtual bus to host Goldfish Android Virtual Devices. - config GOLDFISH_PIPE tristate "Goldfish virtual device for QEMU pipes" ---help--- diff --git a/drivers/platform/goldfish/Makefile b/drivers/platform/goldfish/Makefile index d3487125838c..e0c202df9674 100644 --- a/drivers/platform/goldfish/Makefile +++ b/drivers/platform/goldfish/Makefile @@ -1,5 +1,4 @@ # # Makefile for Goldfish platform specific drivers # -obj-$(CONFIG_GOLDFISH_BUS) += pdev_bus.o obj-$(CONFIG_GOLDFISH_PIPE) += goldfish_pipe.o diff --git a/drivers/platform/goldfish/pdev_bus.c b/drivers/platform/goldfish/pdev_bus.c deleted file mode 100644 index dd9ea463c2a4..000000000000 --- a/drivers/platform/goldfish/pdev_bus.c +++ /dev/null @@ -1,232 +0,0 @@ -/* - * Copyright (C) 2007 Google, Inc. - * Copyright (C) 2011 Intel, Inc. - * Copyright (C) 2013 Intel, Inc. - * - * 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 - -#define PDEV_BUS_OP_DONE (0x00) -#define PDEV_BUS_OP_REMOVE_DEV (0x04) -#define PDEV_BUS_OP_ADD_DEV (0x08) - -#define PDEV_BUS_OP_INIT (0x00) - -#define PDEV_BUS_OP (0x00) -#define PDEV_BUS_GET_NAME (0x04) -#define PDEV_BUS_NAME_LEN (0x08) -#define PDEV_BUS_ID (0x0c) -#define PDEV_BUS_IO_BASE (0x10) -#define PDEV_BUS_IO_SIZE (0x14) -#define PDEV_BUS_IRQ (0x18) -#define PDEV_BUS_IRQ_COUNT (0x1c) -#define PDEV_BUS_GET_NAME_HIGH (0x20) - -struct pdev_bus_dev { - struct list_head list; - struct platform_device pdev; - struct resource resources[0]; -}; - -static void goldfish_pdev_worker(struct work_struct *work); - -static void __iomem *pdev_bus_base; -static unsigned long pdev_bus_addr; -static unsigned long pdev_bus_len; -static u32 pdev_bus_irq; -static LIST_HEAD(pdev_bus_new_devices); -static LIST_HEAD(pdev_bus_registered_devices); -static LIST_HEAD(pdev_bus_removed_devices); -static DECLARE_WORK(pdev_bus_worker, goldfish_pdev_worker); - - -static void goldfish_pdev_worker(struct work_struct *work) -{ - int ret; - struct pdev_bus_dev *pos, *n; - - list_for_each_entry_safe(pos, n, &pdev_bus_removed_devices, list) { - list_del(&pos->list); - platform_device_unregister(&pos->pdev); - kfree(pos); - } - list_for_each_entry_safe(pos, n, &pdev_bus_new_devices, list) { - list_del(&pos->list); - ret = platform_device_register(&pos->pdev); - if (ret) - pr_err("goldfish_pdev_worker failed to register device, %s\n", - pos->pdev.name); - list_add_tail(&pos->list, &pdev_bus_registered_devices); - } -} - -static void goldfish_pdev_remove(void) -{ - struct pdev_bus_dev *pos, *n; - u32 base; - - base = readl(pdev_bus_base + PDEV_BUS_IO_BASE); - - list_for_each_entry_safe(pos, n, &pdev_bus_new_devices, list) { - if (pos->resources[0].start == base) { - list_del(&pos->list); - kfree(pos); - return; - } - } - list_for_each_entry_safe(pos, n, &pdev_bus_registered_devices, list) { - if (pos->resources[0].start == base) { - list_del(&pos->list); - list_add_tail(&pos->list, &pdev_bus_removed_devices); - schedule_work(&pdev_bus_worker); - return; - } - }; - pr_err("goldfish_pdev_remove could not find device at %x\n", base); -} - -static int goldfish_new_pdev(void) -{ - struct pdev_bus_dev *dev; - u32 name_len; - u32 irq = -1, irq_count; - int resource_count = 2; - u32 base; - char *name; - - base = readl(pdev_bus_base + PDEV_BUS_IO_BASE); - - irq_count = readl(pdev_bus_base + PDEV_BUS_IRQ_COUNT); - name_len = readl(pdev_bus_base + PDEV_BUS_NAME_LEN); - if (irq_count) - resource_count++; - - dev = kzalloc(sizeof(*dev) + - sizeof(struct resource) * resource_count + - name_len + 1 + sizeof(*dev->pdev.dev.dma_mask), GFP_ATOMIC); - if (dev == NULL) - return -ENOMEM; - - dev->pdev.num_resources = resource_count; - dev->pdev.resource = (struct resource *)(dev + 1); - dev->pdev.name = name = (char *)(dev->pdev.resource + resource_count); - dev->pdev.dev.coherent_dma_mask = ~0; - dev->pdev.dev.dma_mask = (void *)(dev->pdev.name + name_len + 1); - *dev->pdev.dev.dma_mask = ~0; - -#ifdef CONFIG_64BIT - writel((u32)((u64)name>>32), pdev_bus_base + PDEV_BUS_GET_NAME_HIGH); -#endif - writel((u32)(unsigned long)name, pdev_bus_base + PDEV_BUS_GET_NAME); - name[name_len] = '\0'; - dev->pdev.id = readl(pdev_bus_base + PDEV_BUS_ID); - dev->pdev.resource[0].start = base; - dev->pdev.resource[0].end = base + - readl(pdev_bus_base + PDEV_BUS_IO_SIZE) - 1; - dev->pdev.resource[0].flags = IORESOURCE_MEM; - if (irq_count) { - irq = readl(pdev_bus_base + PDEV_BUS_IRQ); - dev->pdev.resource[1].start = irq; - dev->pdev.resource[1].end = irq + irq_count - 1; - dev->pdev.resource[1].flags = IORESOURCE_IRQ; - } - - pr_debug("goldfish_new_pdev %s at %x irq %d\n", name, base, irq); - list_add_tail(&dev->list, &pdev_bus_new_devices); - schedule_work(&pdev_bus_worker); - - return 0; -} - -static irqreturn_t goldfish_pdev_bus_interrupt(int irq, void *dev_id) -{ - irqreturn_t ret = IRQ_NONE; - - while (1) { - u32 op = readl(pdev_bus_base + PDEV_BUS_OP); - - switch (op) { - case PDEV_BUS_OP_REMOVE_DEV: - goldfish_pdev_remove(); - ret = IRQ_HANDLED; - break; - - case PDEV_BUS_OP_ADD_DEV: - goldfish_new_pdev(); - ret = IRQ_HANDLED; - break; - - case PDEV_BUS_OP_DONE: - default: - return ret; - } - } -} - -static int goldfish_pdev_bus_probe(struct platform_device *pdev) -{ - int ret; - struct resource *r; - - r = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (r == NULL) - return -EINVAL; - - pdev_bus_addr = r->start; - pdev_bus_len = resource_size(r); - - pdev_bus_base = ioremap(pdev_bus_addr, pdev_bus_len); - if (pdev_bus_base == NULL) { - ret = -ENOMEM; - dev_err(&pdev->dev, "unable to map Goldfish MMIO.\n"); - goto free_resources; - } - - r = platform_get_resource(pdev, IORESOURCE_IRQ, 0); - if (r == NULL) { - ret = -ENOENT; - goto free_map; - } - - pdev_bus_irq = r->start; - - ret = request_irq(pdev_bus_irq, goldfish_pdev_bus_interrupt, - IRQF_SHARED, "goldfish_pdev_bus", pdev); - if (ret) { - dev_err(&pdev->dev, "unable to request Goldfish IRQ\n"); - goto free_map; - } - - writel(PDEV_BUS_OP_INIT, pdev_bus_base + PDEV_BUS_OP); - return 0; - -free_map: - iounmap(pdev_bus_base); -free_resources: - release_mem_region(pdev_bus_addr, pdev_bus_len); - return ret; -} - -static struct platform_driver goldfish_pdev_bus_driver = { - .probe = goldfish_pdev_bus_probe, - .driver = { - .name = "goldfish_pdev_bus" - } -}; -builtin_platform_driver(goldfish_pdev_bus_driver); -- cgit v1.2.3 From 45ca3f76de0507ecf143f770570af2942f263812 Mon Sep 17 00:00:00 2001 From: Anton Vasilyev Date: Tue, 24 Jul 2018 18:10:38 +0300 Subject: firmware: vpd: Fix section enabled flag on vpd_section_destroy static struct ro_vpd and rw_vpd are initialized by vpd_sections_init() in vpd_probe() based on header's ro and rw sizes. In vpd_remove() vpd_section_destroy() performs deinitialization based on enabled flag, which is set to true by vpd_sections_init(). This leads to call of vpd_section_destroy() on already destroyed section for probe-release-probe-release sequence if first probe performs ro_vpd initialization and second probe does not initialize it. The patch adds changing enabled flag on vpd_section_destroy and adds cleanup on the error path of vpd_sections_init. Found by Linux Driver Verification project (linuxtesting.org). Signed-off-by: Anton Vasilyev Reviewed-by: Guenter Roeck Signed-off-by: Greg Kroah-Hartman --- drivers/firmware/google/vpd.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/firmware/google/vpd.c b/drivers/firmware/google/vpd.c index e9db895916c3..1aa67bb5d8c0 100644 --- a/drivers/firmware/google/vpd.c +++ b/drivers/firmware/google/vpd.c @@ -246,6 +246,7 @@ static int vpd_section_destroy(struct vpd_section *sec) sysfs_remove_bin_file(vpd_kobj, &sec->bin_attr); kfree(sec->raw_name); memunmap(sec->baseaddr); + sec->enabled = false; } return 0; @@ -279,8 +280,10 @@ static int vpd_sections_init(phys_addr_t physaddr) ret = vpd_section_init("rw", &rw_vpd, physaddr + sizeof(struct vpd_cbmem) + header.ro_size, header.rw_size); - if (ret) + if (ret) { + vpd_section_destroy(&ro_vpd); return ret; + } } return 0; -- cgit v1.2.3 From c8c64b39cf721404d47e7069d7f6d00d03b7f6b4 Mon Sep 17 00:00:00 2001 From: Sherry Yang Date: Thu, 26 Jul 2018 17:17:17 -0700 Subject: android: binder: Show extra_buffers_size in trace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add extra_buffers_size to the binder_transaction_alloc_buf tracepoint. Acked-by: Arve Hjønnevåg Signed-off-by: Sherry Yang Signed-off-by: Greg Kroah-Hartman --- drivers/android/binder_trace.h | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/android/binder_trace.h b/drivers/android/binder_trace.h index 76e3b9c8a8a2..588eb3ec3507 100644 --- a/drivers/android/binder_trace.h +++ b/drivers/android/binder_trace.h @@ -248,14 +248,17 @@ DECLARE_EVENT_CLASS(binder_buffer_class, __field(int, debug_id) __field(size_t, data_size) __field(size_t, offsets_size) + __field(size_t, extra_buffers_size) ), TP_fast_assign( __entry->debug_id = buf->debug_id; __entry->data_size = buf->data_size; __entry->offsets_size = buf->offsets_size; + __entry->extra_buffers_size = buf->extra_buffers_size; ), - TP_printk("transaction=%d data_size=%zd offsets_size=%zd", - __entry->debug_id, __entry->data_size, __entry->offsets_size) + TP_printk("transaction=%d data_size=%zd offsets_size=%zd extra_buffers_size=%zd", + __entry->debug_id, __entry->data_size, __entry->offsets_size, + __entry->extra_buffers_size) ); DEFINE_EVENT(binder_buffer_class, binder_transaction_alloc_buf, -- cgit v1.2.3 From 81ae962d7f180c0092859440c82996cccb254976 Mon Sep 17 00:00:00 2001 From: Anton Vasilyev Date: Fri, 27 Jul 2018 18:45:36 +0300 Subject: misc: ti-st: Fix memory leak in the error path of probe() Free resources instead of direct return of the error code if kim_probe fails. Found by Linux Driver Verification project (linuxtesting.org). Signed-off-by: Anton Vasilyev Signed-off-by: Greg Kroah-Hartman --- drivers/misc/ti-st/st_kim.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/misc/ti-st/st_kim.c b/drivers/misc/ti-st/st_kim.c index 28fba5b3625e..1874ac922166 100644 --- a/drivers/misc/ti-st/st_kim.c +++ b/drivers/misc/ti-st/st_kim.c @@ -755,14 +755,14 @@ static int kim_probe(struct platform_device *pdev) err = gpio_request(kim_gdata->nshutdown, "kim"); if (unlikely(err)) { pr_err(" gpio %d request failed ", kim_gdata->nshutdown); - return err; + goto err_sysfs_group; } /* Configure nShutdown GPIO as output=0 */ err = gpio_direction_output(kim_gdata->nshutdown, 0); if (unlikely(err)) { pr_err(" unable to configure gpio %d", kim_gdata->nshutdown); - return err; + goto err_sysfs_group; } /* get reference of pdev for request_firmware */ -- cgit v1.2.3 From c01513b4b29953e6ba8a8d8de77fb5ca2f01c48f Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Thu, 2 Aug 2018 11:27:21 +0300 Subject: eeprom: idt_89hpesx: clean up an error pointer vs NULL inconsistency We check for IS_ERR_OR_NULL() here, but later we check the same thing for NULL only. It turns out that it can only be NULL so we can make the checking consistent by removing the ERR_PTR stuff. Signed-off-by: Dan Carpenter Signed-off-by: Greg Kroah-Hartman --- drivers/misc/eeprom/idt_89hpesx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/misc/eeprom/idt_89hpesx.c b/drivers/misc/eeprom/idt_89hpesx.c index 119eb05a5a91..8a4659518c33 100644 --- a/drivers/misc/eeprom/idt_89hpesx.c +++ b/drivers/misc/eeprom/idt_89hpesx.c @@ -1128,7 +1128,7 @@ static void idt_get_fw_data(struct idt_89hpesx_dev *pdev) device_for_each_child_node(dev, fwnode) { ee_id = idt_ee_match_id(fwnode); - if (IS_ERR_OR_NULL(ee_id)) { + if (!ee_id) { dev_warn(dev, "Skip unsupported EEPROM device"); continue; } else -- cgit v1.2.3 From f019f07ecf6a6b8bd6d7853bce70925d90af02d1 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Thu, 2 Aug 2018 11:24:47 +0300 Subject: uio: potential double frees if __uio_register_device() fails The uio_unregister_device() function assumes that if "info->uio_dev" is non-NULL that means "info" is fully allocated. Setting info->uio_de has to be the last thing in the function. In the current code, if request_threaded_irq() fails then we return with info->uio_dev set to non-NULL but info is not fully allocated and it can lead to double frees. Fixes: beafc54c4e2f ("UIO: Add the User IO core code") Signed-off-by: Dan Carpenter Signed-off-by: Greg Kroah-Hartman --- drivers/uio/uio.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/uio/uio.c b/drivers/uio/uio.c index 3f76e38e6f30..15ad3469660d 100644 --- a/drivers/uio/uio.c +++ b/drivers/uio/uio.c @@ -953,8 +953,6 @@ int __uio_register_device(struct module *owner, if (ret) goto err_uio_dev_add_attributes; - info->uio_dev = idev; - if (info->irq && (info->irq != UIO_IRQ_CUSTOM)) { /* * Note that we deliberately don't use devm_request_irq @@ -971,6 +969,7 @@ int __uio_register_device(struct module *owner, goto err_request_irq; } + info->uio_dev = idev; return 0; err_request_irq: -- cgit v1.2.3 From b83a4141f025388fd1edeaa48789e3e166e95c7a Mon Sep 17 00:00:00 2001 From: Parth Y Shah Date: Fri, 3 Aug 2018 14:50:43 +0530 Subject: misc: eeprom: assignment outside the if statement Assignment of any variable should be kept outside the if statement Signed-off-by: Parth Y Shah Reviewed-by: Jean Delvare Signed-off-by: Greg Kroah-Hartman --- drivers/misc/eeprom/max6875.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/misc/eeprom/max6875.c b/drivers/misc/eeprom/max6875.c index 0e32709d1022..fc0cf9a7402e 100644 --- a/drivers/misc/eeprom/max6875.c +++ b/drivers/misc/eeprom/max6875.c @@ -148,7 +148,8 @@ static int max6875_probe(struct i2c_client *client, if (client->addr & 1) return -ENODEV; - if (!(data = kzalloc(sizeof(struct max6875_data), GFP_KERNEL))) + data = kzalloc(sizeof(struct max6875_data), GFP_KERNEL); + if (!data) return -ENOMEM; /* A fake client is created on the odd address */ -- cgit v1.2.3 From 7a92719356cac8fef100a8a92e64b583f39368d2 Mon Sep 17 00:00:00 2001 From: "Gustavo A. R. Silva" Date: Sat, 4 Aug 2018 19:37:03 -0500 Subject: genwqe: card_base: Use true and false for boolean values Return statements in functions returning bool should use true or false instead of an integer value. This code was detected with the help of Coccinelle. Signed-off-by: Gustavo A. R. Silva Signed-off-by: Greg Kroah-Hartman --- drivers/misc/genwqe/card_base.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/misc/genwqe/card_base.h b/drivers/misc/genwqe/card_base.h index 1c3967f10f55..120738d6e58b 100644 --- a/drivers/misc/genwqe/card_base.h +++ b/drivers/misc/genwqe/card_base.h @@ -497,7 +497,7 @@ int genwqe_user_vunmap(struct genwqe_dev *cd, struct dma_mapping *m); static inline bool dma_mapping_used(struct dma_mapping *m) { if (!m) - return 0; + return false; return m->size != 0; } -- cgit v1.2.3 From 0171114210c7a3181f64118b0372b0eae2b57649 Mon Sep 17 00:00:00 2001 From: Parth Y Shah Date: Fri, 3 Aug 2018 15:50:38 +0530 Subject: misc: cxl: changed asterisk position Resolved <"foo* bar" should be "foo *bar"> error Signed-off-by: Parth Y Shah Signed-off-by: Greg Kroah-Hartman --- drivers/misc/cxl/fault.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/misc/cxl/fault.c b/drivers/misc/cxl/fault.c index 70dbb6de102c..d45f3e6b17d2 100644 --- a/drivers/misc/cxl/fault.c +++ b/drivers/misc/cxl/fault.c @@ -33,7 +33,7 @@ static bool sste_matches(struct cxl_sste *sste, struct copro_slb *slb) * This finds a free SSTE for the given SLB, or returns NULL if it's already in * the segment table. */ -static struct cxl_sste* find_free_sste(struct cxl_context *ctx, +static struct cxl_sste *find_free_sste(struct cxl_context *ctx, struct copro_slb *slb) { struct cxl_sste *primary, *sste, *ret = NULL; -- cgit v1.2.3 From a39284ae9d2ad09975c8ae33f1bd0f05fbfbf6ee Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Thu, 2 Aug 2018 11:42:22 +0300 Subject: misc: mic: SCIF Fix scif_get_new_port() error handling There are only 2 callers of scif_get_new_port() and both appear to get the error handling wrong. Both treat zero returns as error, but it actually returns negative error codes and >= 0 on success. Fixes: e9089f43c9a7 ("misc: mic: SCIF open close bind and listen APIs") Signed-off-by: Dan Carpenter Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mic/scif/scif_api.c | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/drivers/misc/mic/scif/scif_api.c b/drivers/misc/mic/scif/scif_api.c index 463f06d0b4ef..8dd0ccedeb94 100644 --- a/drivers/misc/mic/scif/scif_api.c +++ b/drivers/misc/mic/scif/scif_api.c @@ -371,11 +371,10 @@ int scif_bind(scif_epd_t epd, u16 pn) goto scif_bind_exit; } } else { - pn = scif_get_new_port(); - if (!pn) { - ret = -ENOSPC; + ret = scif_get_new_port(); + if (ret < 0) goto scif_bind_exit; - } + pn = ret; } ep->state = SCIFEP_BOUND; @@ -649,13 +648,12 @@ int __scif_connect(scif_epd_t epd, struct scif_port_id *dst, bool non_block) err = -EISCONN; break; case SCIFEP_UNBOUND: - ep->port.port = scif_get_new_port(); - if (!ep->port.port) { - err = -ENOSPC; - } else { - ep->port.node = scif_info.nodeid; - ep->conn_async_state = ASYNC_CONN_IDLE; - } + err = scif_get_new_port(); + if (err < 0) + break; + ep->port.port = err; + ep->port.node = scif_info.nodeid; + ep->conn_async_state = ASYNC_CONN_IDLE; /* Fall through */ case SCIFEP_BOUND: /* -- cgit v1.2.3 From aa1221b2584f06066d1e6b22ef7950fb12d94864 Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Mon, 6 Aug 2018 12:22:37 +1000 Subject: fsi: scom: Fix NULL dereference The chardev conversion forgot to copy the fsi_dev, silly mistake, compounded by a testing mistake on my side, this specific driver wasn't being tested properly. Fixes: d8f4587655f9 "fsi: scom: Convert to use the new chardev" Signed-off-by: Benjamin Herrenschmidt --- drivers/fsi/fsi-scom.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/fsi/fsi-scom.c b/drivers/fsi/fsi-scom.c index 0f303a700f69..df94021dd9d1 100644 --- a/drivers/fsi/fsi-scom.c +++ b/drivers/fsi/fsi-scom.c @@ -598,6 +598,7 @@ static int scom_probe(struct device *dev) kfree(scom); return -ENODEV; } + scom->fsi_dev = fsi_dev; /* Create chardev for userspace access */ scom->dev.type = &fsi_cdev_type; -- cgit v1.2.3 From 15e2a7218c2788d79c5633336d17cb9428c221e7 Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Tue, 7 Aug 2018 11:51:52 +1000 Subject: fsi: sbefifo: Bump max command length Otherwise cronus putmem fails istep and BML fails to upload skiboot To do that, we still use our one-page command buffer for small commands for speed, and for anything bigger, with a limit of 1MB plus a page, we vmalloc a temporary buffer. The limit was chosen because Cronus will break up any data transfer into 1M chunks (the extra page is for the command header). Signed-off-by: Benjamin Herrenschmidt Reviewed-by: Andrew Jeffery --- drivers/fsi/fsi-sbefifo.c | 39 ++++++++++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/drivers/fsi/fsi-sbefifo.c b/drivers/fsi/fsi-sbefifo.c index 7550497da8ef..ae861342626e 100644 --- a/drivers/fsi/fsi-sbefifo.c +++ b/drivers/fsi/fsi-sbefifo.c @@ -30,6 +30,7 @@ #include #include #include +#include /* * The SBEFIFO is a pipe-like FSI device for communicating with @@ -110,7 +111,7 @@ enum sbe_state #define SBEFIFO_TIMEOUT_IN_RSP 1000 /* Other constants */ -#define SBEFIFO_MAX_CMD_LEN PAGE_SIZE +#define SBEFIFO_MAX_USER_CMD_LEN (0x100000 + PAGE_SIZE) #define SBEFIFO_RESET_MAGIC 0x52534554 /* "RSET" */ struct sbefifo { @@ -128,6 +129,7 @@ struct sbefifo { struct sbefifo_user { struct sbefifo *sbefifo; struct mutex file_lock; + void *cmd_page; void *pending_cmd; size_t pending_len; }; @@ -726,7 +728,7 @@ int sbefifo_submit(struct device *dev, const __be32 *command, size_t cmd_len, return -ENODEV; if (WARN_ON_ONCE(sbefifo->magic != SBEFIFO_MAGIC)) return -ENODEV; - if (!resp_len || !command || !response || cmd_len > SBEFIFO_MAX_CMD_LEN) + if (!resp_len || !command || !response) return -EINVAL; /* Prepare iov iterator */ @@ -751,6 +753,15 @@ EXPORT_SYMBOL_GPL(sbefifo_submit); /* * Char device interface */ + +static void sbefifo_release_command(struct sbefifo_user *user) +{ + if (is_vmalloc_addr(user->pending_cmd)) + vfree(user->pending_cmd); + user->pending_cmd = NULL; + user->pending_len = 0; +} + static int sbefifo_user_open(struct inode *inode, struct file *file) { struct sbefifo *sbefifo = container_of(inode->i_cdev, struct sbefifo, cdev); @@ -762,8 +773,8 @@ static int sbefifo_user_open(struct inode *inode, struct file *file) file->private_data = user; user->sbefifo = sbefifo; - user->pending_cmd = (void *)__get_free_page(GFP_KERNEL); - if (!user->pending_cmd) { + user->cmd_page = (void *)__get_free_page(GFP_KERNEL); + if (!user->cmd_page) { kfree(user); return -ENOMEM; } @@ -816,7 +827,7 @@ static ssize_t sbefifo_user_read(struct file *file, char __user *buf, /* Extract the response length */ rc = len - iov_iter_count(&resp_iter); bail: - user->pending_len = 0; + sbefifo_release_command(user); mutex_unlock(&user->file_lock); return rc; } @@ -831,13 +842,23 @@ static ssize_t sbefifo_user_write(struct file *file, const char __user *buf, if (!user) return -EINVAL; sbefifo = user->sbefifo; - if (len > SBEFIFO_MAX_CMD_LEN) + if (len > SBEFIFO_MAX_USER_CMD_LEN) return -EINVAL; if (len & 3) return -EINVAL; mutex_lock(&user->file_lock); + /* Can we use the pre-allocate buffer ? If not, allocate */ + if (len <= PAGE_SIZE) + user->pending_cmd = user->cmd_page; + else + user->pending_cmd = vmalloc(len); + if (!user->pending_cmd) { + rc = -ENOMEM; + goto bail; + } + /* Copy the command into the staging buffer */ if (copy_from_user(user->pending_cmd, buf, len)) { rc = -EFAULT; @@ -863,6 +884,9 @@ static ssize_t sbefifo_user_write(struct file *file, const char __user *buf, /* Update the staging buffer size */ user->pending_len = len; bail: + if (!user->pending_len) + sbefifo_release_command(user); + mutex_unlock(&user->file_lock); /* And that's it, we'll issue the command on a read */ @@ -876,7 +900,8 @@ static int sbefifo_user_release(struct inode *inode, struct file *file) if (!user) return -EINVAL; - free_page((unsigned long)user->pending_cmd); + sbefifo_release_command(user); + free_page((unsigned long)user->cmd_page); kfree(user); return 0; -- cgit v1.2.3 From 128f38041035001276e964cda1cf951f218d965d Mon Sep 17 00:00:00 2001 From: Sherry Yang Date: Tue, 7 Aug 2018 12:57:13 -0700 Subject: android: binder: Rate-limit debug and userspace triggered err msgs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use rate-limited debug messages where userspace can trigger excessive log spams. Acked-by: Arve Hjønnevåg Signed-off-by: Sherry Yang Signed-off-by: Greg Kroah-Hartman --- drivers/android/binder.c | 5 +++-- drivers/android/binder_alloc.c | 41 ++++++++++++++++++++++++++--------------- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/drivers/android/binder.c b/drivers/android/binder.c index 1cc2fa16af8b..d58763b6b009 100644 --- a/drivers/android/binder.c +++ b/drivers/android/binder.c @@ -70,6 +70,7 @@ #include #include #include +#include #include @@ -163,13 +164,13 @@ module_param_call(stop_on_user_error, binder_set_stop_on_user_error, #define binder_debug(mask, x...) \ do { \ if (binder_debug_mask & mask) \ - pr_info(x); \ + pr_info_ratelimited(x); \ } while (0) #define binder_user_error(x...) \ do { \ if (binder_debug_mask & BINDER_DEBUG_USER_ERROR) \ - pr_info(x); \ + pr_info_ratelimited(x); \ if (binder_stop_on_user_error) \ binder_stop_on_user_error = 2; \ } while (0) diff --git a/drivers/android/binder_alloc.c b/drivers/android/binder_alloc.c index 2c258dcf9d72..3f3b7b253445 100644 --- a/drivers/android/binder_alloc.c +++ b/drivers/android/binder_alloc.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include "binder_alloc.h" #include "binder_trace.h" @@ -36,11 +37,12 @@ struct list_lru binder_alloc_lru; static DEFINE_MUTEX(binder_alloc_mmap_lock); enum { + BINDER_DEBUG_USER_ERROR = 1U << 0, BINDER_DEBUG_OPEN_CLOSE = 1U << 1, BINDER_DEBUG_BUFFER_ALLOC = 1U << 2, BINDER_DEBUG_BUFFER_ALLOC_ASYNC = 1U << 3, }; -static uint32_t binder_alloc_debug_mask; +static uint32_t binder_alloc_debug_mask = BINDER_DEBUG_USER_ERROR; module_param_named(debug_mask, binder_alloc_debug_mask, uint, 0644); @@ -48,7 +50,7 @@ module_param_named(debug_mask, binder_alloc_debug_mask, #define binder_alloc_debug(mask, x...) \ do { \ if (binder_alloc_debug_mask & mask) \ - pr_info(x); \ + pr_info_ratelimited(x); \ } while (0) static struct binder_buffer *binder_buffer_next(struct binder_buffer *buffer) @@ -152,8 +154,10 @@ static struct binder_buffer *binder_alloc_prepare_to_free_locked( * free the buffer twice */ if (buffer->free_in_progress) { - pr_err("%d:%d FREE_BUFFER u%016llx user freed buffer twice\n", - alloc->pid, current->pid, (u64)user_ptr); + binder_alloc_debug(BINDER_DEBUG_USER_ERROR, + "%d:%d FREE_BUFFER u%016llx user freed buffer twice\n", + alloc->pid, current->pid, + (u64)user_ptr); return NULL; } buffer->free_in_progress = 1; @@ -224,8 +228,9 @@ static int binder_update_page_range(struct binder_alloc *alloc, int allocate, } if (!vma && need_mm) { - pr_err("%d: binder_alloc_buf failed to map pages in userspace, no vma\n", - alloc->pid); + binder_alloc_debug(BINDER_DEBUG_USER_ERROR, + "%d: binder_alloc_buf failed to map pages in userspace, no vma\n", + alloc->pid); goto err_no_vma; } @@ -344,8 +349,9 @@ static struct binder_buffer *binder_alloc_new_buf_locked( int ret; if (alloc->vma == NULL) { - pr_err("%d: binder_alloc_buf, no vma\n", - alloc->pid); + binder_alloc_debug(BINDER_DEBUG_USER_ERROR, + "%d: binder_alloc_buf, no vma\n", + alloc->pid); return ERR_PTR(-ESRCH); } @@ -417,11 +423,14 @@ static struct binder_buffer *binder_alloc_new_buf_locked( if (buffer_size > largest_free_size) largest_free_size = buffer_size; } - pr_err("%d: binder_alloc_buf size %zd failed, no address space\n", - alloc->pid, size); - pr_err("allocated: %zd (num: %zd largest: %zd), free: %zd (num: %zd largest: %zd)\n", - total_alloc_size, allocated_buffers, largest_alloc_size, - total_free_size, free_buffers, largest_free_size); + binder_alloc_debug(BINDER_DEBUG_USER_ERROR, + "%d: binder_alloc_buf size %zd failed, no address space\n", + alloc->pid, size); + binder_alloc_debug(BINDER_DEBUG_USER_ERROR, + "allocated: %zd (num: %zd largest: %zd), free: %zd (num: %zd largest: %zd)\n", + total_alloc_size, allocated_buffers, + largest_alloc_size, total_free_size, + free_buffers, largest_free_size); return ERR_PTR(-ENOSPC); } if (n == NULL) { @@ -731,8 +740,10 @@ err_alloc_pages_failed: err_get_vm_area_failed: err_already_mapped: mutex_unlock(&binder_alloc_mmap_lock); - pr_err("%s: %d %lx-%lx %s failed %d\n", __func__, - alloc->pid, vma->vm_start, vma->vm_end, failure_string, ret); + binder_alloc_debug(BINDER_DEBUG_USER_ERROR, + "%s: %d %lx-%lx %s failed %d\n", __func__, + alloc->pid, vma->vm_start, vma->vm_end, + failure_string, ret); return ret; } -- cgit v1.2.3