From 5be149bdd36c8765c9e785f70b888d028ada40af Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Wed, 19 Oct 2016 16:33:27 +0300 Subject: mei: bus: add module_mei_cl_driver helper macro Add module_mei_cl_driver helper macro for eliminating the boilerplate code from mei_cl drivers registration. The macro is intended for drivers which in their init/exit sections does only register/unregister of a mei_cl driver. Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- include/linux/mei_cl_bus.h | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'include/linux') diff --git a/include/linux/mei_cl_bus.h b/include/linux/mei_cl_bus.h index e746919530f5..e6fbd98ea90e 100644 --- a/include/linux/mei_cl_bus.h +++ b/include/linux/mei_cl_bus.h @@ -74,6 +74,19 @@ int __mei_cldev_driver_register(struct mei_cl_driver *cldrv, void mei_cldev_driver_unregister(struct mei_cl_driver *cldrv); +/** + * module_mei_cl_driver - Helper macro for registering mei cl driver + * + * @__mei_cldrv mei_cl_driver structure + * + * Helper macro for mei cl drivers which do not do anything special in module + * init/exit, for eliminating a boilerplate code. + */ +#define module_mei_cl_driver(__mei_cldrv) \ + module_driver(__mei_cldrv, \ + mei_cldev_driver_register,\ + mei_cldev_driver_unregister) + ssize_t mei_cldev_send(struct mei_cl_device *cldev, u8 *buf, size_t length); ssize_t mei_cldev_recv(struct mei_cl_device *cldev, u8 *buf, size_t length); -- cgit v1.2.3-71-gd317 From 1e4edb3fe93ff9f7b678a8f1b8d9df717edf6ad9 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Wed, 19 Oct 2016 16:33:31 +0300 Subject: mei: bus: remove rx callback context The callback context is redunant as all the information can be retrived from the device struture of its private data. Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/bus.c | 6 ++---- drivers/nfc/mei_phy.c | 5 ++--- drivers/watchdog/mei_wdt.c | 6 ++---- include/linux/mei_cl_bus.h | 6 ++---- 4 files changed, 8 insertions(+), 15 deletions(-) (limited to 'include/linux') diff --git a/drivers/misc/mei/bus.c b/drivers/misc/mei/bus.c index 8cac7ef9ad0d..89a694ca624c 100644 --- a/drivers/misc/mei/bus.c +++ b/drivers/misc/mei/bus.c @@ -228,7 +228,7 @@ static void mei_cl_bus_event_work(struct work_struct *work) bus = cldev->bus; if (cldev->event_cb) - cldev->event_cb(cldev, cldev->events, cldev->event_context); + cldev->event_cb(cldev, cldev->events); cldev->events = 0; @@ -301,7 +301,6 @@ bool mei_cl_bus_rx_event(struct mei_cl *cl) * @cldev: me client devices * @event_cb: callback function * @events_mask: requested events bitmask - * @context: driver context data * * Return: 0 on success * -EALREADY if an callback is already registered @@ -309,7 +308,7 @@ bool mei_cl_bus_rx_event(struct mei_cl *cl) */ int mei_cldev_register_event_cb(struct mei_cl_device *cldev, unsigned long events_mask, - mei_cldev_event_cb_t event_cb, void *context) + mei_cldev_event_cb_t event_cb) { struct mei_device *bus = cldev->bus; int ret; @@ -320,7 +319,6 @@ int mei_cldev_register_event_cb(struct mei_cl_device *cldev, cldev->events = 0; cldev->events_mask = events_mask; cldev->event_cb = event_cb; - cldev->event_context = context; INIT_WORK(&cldev->event_work, mei_cl_bus_event_work); if (cldev->events_mask & BIT(MEI_CL_EVENT_RX)) { diff --git a/drivers/nfc/mei_phy.c b/drivers/nfc/mei_phy.c index 66dfd81ffb18..07b4239585fa 100644 --- a/drivers/nfc/mei_phy.c +++ b/drivers/nfc/mei_phy.c @@ -297,8 +297,7 @@ static int mei_nfc_recv(struct nfc_mei_phy *phy, u8 *buf, size_t length) } -static void nfc_mei_event_cb(struct mei_cl_device *cldev, u32 events, - void *context) +static void nfc_mei_event_cb(struct mei_cl_device *cldev, u32 events) { struct nfc_mei_phy *phy = mei_cldev_get_drvdata(cldev); @@ -360,7 +359,7 @@ static int nfc_mei_phy_enable(void *phy_id) } r = mei_cldev_register_event_cb(phy->cldev, BIT(MEI_CL_EVENT_RX), - nfc_mei_event_cb, phy); + nfc_mei_event_cb); if (r) { pr_err("Event cb registration failed %d\n", r); goto err; diff --git a/drivers/watchdog/mei_wdt.c b/drivers/watchdog/mei_wdt.c index 116be477c8fd..e0af52265511 100644 --- a/drivers/watchdog/mei_wdt.c +++ b/drivers/watchdog/mei_wdt.c @@ -501,10 +501,8 @@ static void mei_wdt_notify_event(struct mei_cl_device *cldev) * * @cldev: bus device * @events: event mask - * @context: callback context */ -static void mei_wdt_event(struct mei_cl_device *cldev, - u32 events, void *context) +static void mei_wdt_event(struct mei_cl_device *cldev, u32 events) { if (events & BIT(MEI_CL_EVENT_RX)) mei_wdt_event_rx(cldev); @@ -626,7 +624,7 @@ static int mei_wdt_probe(struct mei_cl_device *cldev, ret = mei_cldev_register_event_cb(wdt->cldev, BIT(MEI_CL_EVENT_RX) | BIT(MEI_CL_EVENT_NOTIF), - mei_wdt_event, NULL); + mei_wdt_event); /* on legacy devices notification is not supported * this doesn't fail the registration for RX event diff --git a/include/linux/mei_cl_bus.h b/include/linux/mei_cl_bus.h index e6fbd98ea90e..4adb2e7c9f84 100644 --- a/include/linux/mei_cl_bus.h +++ b/include/linux/mei_cl_bus.h @@ -9,7 +9,7 @@ struct mei_cl_device; struct mei_device; typedef void (*mei_cldev_event_cb_t)(struct mei_cl_device *cldev, - u32 events, void *context); + u32 events); /** * struct mei_cl_device - MEI device handle @@ -27,7 +27,6 @@ typedef void (*mei_cldev_event_cb_t)(struct mei_cl_device *cldev, * @event_work: async work to execute event callback * @event_cb: Drivers register this callback to get asynchronous ME * events (e.g. Rx buffer pending) notifications. - * @event_context: event callback run context * @events_mask: Events bit mask requested by driver. * @events: Events bitmask sent to the driver. * @@ -46,7 +45,6 @@ struct mei_cl_device { struct work_struct event_work; mei_cldev_event_cb_t event_cb; - void *event_context; unsigned long events_mask; unsigned long events; @@ -92,7 +90,7 @@ ssize_t mei_cldev_recv(struct mei_cl_device *cldev, u8 *buf, size_t length); int mei_cldev_register_event_cb(struct mei_cl_device *cldev, unsigned long event_mask, - mei_cldev_event_cb_t read_cb, void *context); + mei_cldev_event_cb_t read_cb); #define MEI_CL_EVENT_RX 0 #define MEI_CL_EVENT_TX 1 -- cgit v1.2.3-71-gd317 From f71082f815bf0b9599b97546cf1dd057c789c3c1 Mon Sep 17 00:00:00 2001 From: Paul Gortmaker Date: Sat, 22 Oct 2016 18:36:05 -0400 Subject: miscdevice: introduce builtin_misc_device This is basically the same as module_misc_device but without the presence of an exit/remove function. Similar in nature to the commit f309d4443130bf814e991f836e919dca22df37ae ("platform_device: better support builtin boilerplate avoidance"). Cc: Arnd Bergmann Signed-off-by: Paul Gortmaker Signed-off-by: Greg Kroah-Hartman --- include/linux/miscdevice.h | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'include/linux') diff --git a/include/linux/miscdevice.h b/include/linux/miscdevice.h index 722698a43d79..a426cb55dc43 100644 --- a/include/linux/miscdevice.h +++ b/include/linux/miscdevice.h @@ -71,6 +71,13 @@ struct miscdevice { extern int misc_register(struct miscdevice *misc); extern void misc_deregister(struct miscdevice *misc); +/* + * Helper macro for drivers that don't do anything special in the initcall. + * This helps in eleminating of boilerplate code. + */ +#define builtin_misc_device(__misc_device) \ + builtin_driver(__misc_device, misc_register) + /* * Helper macro for drivers that don't do anything special in module init / exit * call. This helps in eleminating of boilerplate code. -- cgit v1.2.3-71-gd317 From fa32ff6576623616c1751562edaed8c164ca5199 Mon Sep 17 00:00:00 2001 From: Vitaly Kuznetsov Date: Sun, 6 Nov 2016 13:14:05 -0800 Subject: Drivers: hv: ring_buffer: count on wrap around mappings in get_next_pkt_raw() (v2) With wrap around mappings in place we can always provide drivers with direct links to packets on the ring buffer, even when they wrap around. Do the required updates to get_next_pkt_raw()/put_pkt_raw() The first version of this commit was reverted (65a532f3d50a) to deal with cross-tree merge issues which are (hopefully) resolved now. Signed-off-by: Vitaly Kuznetsov Signed-off-by: K. Y. Srinivasan Tested-by: Dexuan Cui Signed-off-by: Greg Kroah-Hartman --- include/linux/hyperv.h | 32 +++++++++++--------------------- 1 file changed, 11 insertions(+), 21 deletions(-) (limited to 'include/linux') diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index 6824556d37ed..42ae6a5a2538 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -1526,31 +1526,23 @@ static inline struct vmpacket_descriptor * get_next_pkt_raw(struct vmbus_channel *channel) { struct hv_ring_buffer_info *ring_info = &channel->inbound; - u32 read_loc = ring_info->priv_read_index; + u32 priv_read_loc = ring_info->priv_read_index; void *ring_buffer = hv_get_ring_buffer(ring_info); - struct vmpacket_descriptor *cur_desc; - u32 packetlen; u32 dsize = ring_info->ring_datasize; - u32 delta = read_loc - ring_info->ring_buffer->read_index; + /* + * delta is the difference between what is available to read and + * what was already consumed in place. We commit read index after + * the whole batch is processed. + */ + u32 delta = priv_read_loc >= ring_info->ring_buffer->read_index ? + priv_read_loc - ring_info->ring_buffer->read_index : + (dsize - ring_info->ring_buffer->read_index) + priv_read_loc; u32 bytes_avail_toread = (hv_get_bytes_to_read(ring_info) - delta); if (bytes_avail_toread < sizeof(struct vmpacket_descriptor)) return NULL; - if ((read_loc + sizeof(*cur_desc)) > dsize) - return NULL; - - cur_desc = ring_buffer + read_loc; - packetlen = cur_desc->len8 << 3; - - /* - * If the packet under consideration is wrapping around, - * return failure. - */ - if ((read_loc + packetlen + VMBUS_PKT_TRAILER) > (dsize - 1)) - return NULL; - - return cur_desc; + return ring_buffer + priv_read_loc; } /* @@ -1562,16 +1554,14 @@ static inline void put_pkt_raw(struct vmbus_channel *channel, struct vmpacket_descriptor *desc) { struct hv_ring_buffer_info *ring_info = &channel->inbound; - u32 read_loc = ring_info->priv_read_index; u32 packetlen = desc->len8 << 3; u32 dsize = ring_info->ring_datasize; - if ((read_loc + packetlen + VMBUS_PKT_TRAILER) > dsize) - BUG(); /* * Include the packet trailer. */ ring_info->priv_read_index += packetlen + VMBUS_PKT_TRAILER; + ring_info->priv_read_index %= dsize; } /* -- cgit v1.2.3-71-gd317 From 1f6ee4e7d83586c8b10bd4f2f4346353d04ce884 Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Sun, 6 Nov 2016 13:14:17 -0800 Subject: Drivers: hv: vmbus: On write cleanup the logic to interrupt the host Signal the host when we determine the host is to be signaled. The currrent code determines the need to signal in the ringbuffer code and actually issues the signal elsewhere. This can result in the host viewing this interrupt as spurious since the host may also poll the channel. Make the necessary adjustments. Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman --- drivers/hv/channel.c | 99 +++++------------------------------------------ drivers/hv/hyperv_vmbus.h | 6 +-- drivers/hv/ring_buffer.c | 30 +++++++++----- include/linux/hyperv.h | 1 + 4 files changed, 35 insertions(+), 101 deletions(-) (limited to 'include/linux') diff --git a/drivers/hv/channel.c b/drivers/hv/channel.c index 5e482d7f60cb..8a8148f7b842 100644 --- a/drivers/hv/channel.c +++ b/drivers/hv/channel.c @@ -39,7 +39,7 @@ * vmbus_setevent- Trigger an event notification on the specified * channel. */ -static void vmbus_setevent(struct vmbus_channel *channel) +void vmbus_setevent(struct vmbus_channel *channel) { struct hv_monitor_page *monitorpage; @@ -65,6 +65,7 @@ static void vmbus_setevent(struct vmbus_channel *channel) vmbus_set_event(channel); } } +EXPORT_SYMBOL_GPL(vmbus_setevent); /* * vmbus_open - Open the specified channel. @@ -635,8 +636,6 @@ int vmbus_sendpacket_ctl(struct vmbus_channel *channel, void *buffer, u32 packetlen_aligned = ALIGN(packetlen, sizeof(u64)); struct kvec bufferlist[3]; u64 aligned_data = 0; - int ret; - bool signal = false; bool lock = channel->acquire_ring_lock; int num_vecs = ((bufferlen != 0) ? 3 : 1); @@ -656,41 +655,9 @@ int vmbus_sendpacket_ctl(struct vmbus_channel *channel, void *buffer, bufferlist[2].iov_base = &aligned_data; bufferlist[2].iov_len = (packetlen_aligned - packetlen); - ret = hv_ringbuffer_write(&channel->outbound, bufferlist, num_vecs, - &signal, lock, channel->signal_policy); - - /* - * Signalling the host is conditional on many factors: - * 1. The ring state changed from being empty to non-empty. - * This is tracked by the variable "signal". - * 2. The variable kick_q tracks if more data will be placed - * on the ring. We will not signal if more data is - * to be placed. - * - * Based on the channel signal state, we will decide - * which signaling policy will be applied. - * - * If we cannot write to the ring-buffer; signal the host - * even if we may not have written anything. This is a rare - * enough condition that it should not matter. - * NOTE: in this case, the hvsock channel is an exception, because - * it looks the host side's hvsock implementation has a throttling - * mechanism which can hurt the performance otherwise. - * - * KYS: Oct. 30, 2016: - * It looks like Windows hosts have logic to deal with DOS attacks that - * can be triggered if it receives interrupts when it is not expecting - * the interrupt. The host expects interrupts only when the ring - * transitions from empty to non-empty (or full to non full on the guest - * to host ring). - * So, base the signaling decision solely on the ring state until the - * host logic is fixed. - */ - - if (((ret == 0) && signal)) - vmbus_setevent(channel); + return hv_ringbuffer_write(channel, bufferlist, num_vecs, + lock, kick_q); - return ret; } EXPORT_SYMBOL(vmbus_sendpacket_ctl); @@ -731,7 +698,6 @@ int vmbus_sendpacket_pagebuffer_ctl(struct vmbus_channel *channel, u32 flags, bool kick_q) { - int ret; int i; struct vmbus_channel_packet_page_buffer desc; u32 descsize; @@ -739,7 +705,6 @@ int vmbus_sendpacket_pagebuffer_ctl(struct vmbus_channel *channel, u32 packetlen_aligned; struct kvec bufferlist[3]; u64 aligned_data = 0; - bool signal = false; bool lock = channel->acquire_ring_lock; if (pagecount > MAX_PAGE_BUFFER_COUNT) @@ -777,38 +742,8 @@ int vmbus_sendpacket_pagebuffer_ctl(struct vmbus_channel *channel, bufferlist[2].iov_base = &aligned_data; bufferlist[2].iov_len = (packetlen_aligned - packetlen); - ret = hv_ringbuffer_write(&channel->outbound, bufferlist, 3, - &signal, lock, channel->signal_policy); - - /* - * Signalling the host is conditional on many factors: - * 1. The ring state changed from being empty to non-empty. - * This is tracked by the variable "signal". - * 2. The variable kick_q tracks if more data will be placed - * on the ring. We will not signal if more data is - * to be placed. - * - * Based on the channel signal state, we will decide - * which signaling policy will be applied. - * - * If we cannot write to the ring-buffer; signal the host - * even if we may not have written anything. This is a rare - * enough condition that it should not matter. - * - * KYS: Oct. 30, 2016: - * It looks like Windows hosts have logic to deal with DOS attacks that - * can be triggered if it receives interrupts when it is not expecting - * the interrupt. The host expects interrupts only when the ring - * transitions from empty to non-empty (or full to non full on the guest - * to host ring). - * So, base the signaling decision solely on the ring state until the - * host logic is fixed. - */ - - if (((ret == 0) && signal)) - vmbus_setevent(channel); - - return ret; + return hv_ringbuffer_write(channel, bufferlist, 3, + lock, kick_q); } EXPORT_SYMBOL_GPL(vmbus_sendpacket_pagebuffer_ctl); @@ -839,12 +774,10 @@ int vmbus_sendpacket_mpb_desc(struct vmbus_channel *channel, u32 desc_size, void *buffer, u32 bufferlen, u64 requestid) { - int ret; u32 packetlen; u32 packetlen_aligned; struct kvec bufferlist[3]; u64 aligned_data = 0; - bool signal = false; bool lock = channel->acquire_ring_lock; packetlen = desc_size + bufferlen; @@ -865,13 +798,8 @@ int vmbus_sendpacket_mpb_desc(struct vmbus_channel *channel, bufferlist[2].iov_base = &aligned_data; bufferlist[2].iov_len = (packetlen_aligned - packetlen); - ret = hv_ringbuffer_write(&channel->outbound, bufferlist, 3, - &signal, lock, channel->signal_policy); - - if (ret == 0 && signal) - vmbus_setevent(channel); - - return ret; + return hv_ringbuffer_write(channel, bufferlist, 3, + lock, true); } EXPORT_SYMBOL_GPL(vmbus_sendpacket_mpb_desc); @@ -883,14 +811,12 @@ int vmbus_sendpacket_multipagebuffer(struct vmbus_channel *channel, struct hv_multipage_buffer *multi_pagebuffer, void *buffer, u32 bufferlen, u64 requestid) { - int ret; struct vmbus_channel_packet_multipage_buffer desc; u32 descsize; u32 packetlen; u32 packetlen_aligned; struct kvec bufferlist[3]; u64 aligned_data = 0; - bool signal = false; bool lock = channel->acquire_ring_lock; u32 pfncount = NUM_PAGES_SPANNED(multi_pagebuffer->offset, multi_pagebuffer->len); @@ -930,13 +856,8 @@ int vmbus_sendpacket_multipagebuffer(struct vmbus_channel *channel, bufferlist[2].iov_base = &aligned_data; bufferlist[2].iov_len = (packetlen_aligned - packetlen); - ret = hv_ringbuffer_write(&channel->outbound, bufferlist, 3, - &signal, lock, channel->signal_policy); - - if (ret == 0 && signal) - vmbus_setevent(channel); - - return ret; + return hv_ringbuffer_write(channel, bufferlist, 3, + lock, true); } EXPORT_SYMBOL_GPL(vmbus_sendpacket_multipagebuffer); diff --git a/drivers/hv/hyperv_vmbus.h b/drivers/hv/hyperv_vmbus.h index bdab7e7f2a15..2d42ebe2af6c 100644 --- a/drivers/hv/hyperv_vmbus.h +++ b/drivers/hv/hyperv_vmbus.h @@ -527,10 +527,10 @@ int hv_ringbuffer_init(struct hv_ring_buffer_info *ring_info, void hv_ringbuffer_cleanup(struct hv_ring_buffer_info *ring_info); -int hv_ringbuffer_write(struct hv_ring_buffer_info *ring_info, +int hv_ringbuffer_write(struct vmbus_channel *channel, struct kvec *kv_list, - u32 kv_count, bool *signal, bool lock, - enum hv_signal_policy policy); + u32 kv_count, bool lock, + bool kick_q); int hv_ringbuffer_read(struct hv_ring_buffer_info *inring_info, void *buffer, u32 buflen, u32 *buffer_actual_len, diff --git a/drivers/hv/ring_buffer.c b/drivers/hv/ring_buffer.c index 5d11d93eedf4..4af71306d0ff 100644 --- a/drivers/hv/ring_buffer.c +++ b/drivers/hv/ring_buffer.c @@ -66,14 +66,25 @@ u32 hv_end_read(struct hv_ring_buffer_info *rbi) * once the ring buffer is empty, it will clear the * interrupt_mask and re-check to see if new data has * arrived. + * + * KYS: Oct. 30, 2016: + * It looks like Windows hosts have logic to deal with DOS attacks that + * can be triggered if it receives interrupts when it is not expecting + * the interrupt. The host expects interrupts only when the ring + * transitions from empty to non-empty (or full to non full on the guest + * to host ring). + * So, base the signaling decision solely on the ring state until the + * host logic is fixed. */ -static bool hv_need_to_signal(u32 old_write, struct hv_ring_buffer_info *rbi, - enum hv_signal_policy policy) +static void hv_signal_on_write(u32 old_write, struct vmbus_channel *channel, + bool kick_q) { + struct hv_ring_buffer_info *rbi = &channel->outbound; + virt_mb(); if (READ_ONCE(rbi->ring_buffer->interrupt_mask)) - return false; + return; /* check interrupt_mask before read_index */ virt_rmb(); @@ -82,9 +93,9 @@ static bool hv_need_to_signal(u32 old_write, struct hv_ring_buffer_info *rbi, * ring transitions from being empty to non-empty. */ if (old_write == READ_ONCE(rbi->ring_buffer->read_index)) - return true; + vmbus_setevent(channel); - return false; + return; } /* Get the next write location for the specified ring buffer. */ @@ -273,9 +284,9 @@ void hv_ringbuffer_cleanup(struct hv_ring_buffer_info *ring_info) } /* Write to the ring buffer. */ -int hv_ringbuffer_write(struct hv_ring_buffer_info *outring_info, - struct kvec *kv_list, u32 kv_count, bool *signal, bool lock, - enum hv_signal_policy policy) +int hv_ringbuffer_write(struct vmbus_channel *channel, + struct kvec *kv_list, u32 kv_count, bool lock, + bool kick_q) { int i = 0; u32 bytes_avail_towrite; @@ -285,6 +296,7 @@ int hv_ringbuffer_write(struct hv_ring_buffer_info *outring_info, u32 old_write; u64 prev_indices = 0; unsigned long flags = 0; + struct hv_ring_buffer_info *outring_info = &channel->outbound; for (i = 0; i < kv_count; i++) totalbytes_towrite += kv_list[i].iov_len; @@ -337,7 +349,7 @@ int hv_ringbuffer_write(struct hv_ring_buffer_info *outring_info, if (lock) spin_unlock_irqrestore(&outring_info->ring_lock, flags); - *signal = hv_need_to_signal(old_write, outring_info, policy); + hv_signal_on_write(old_write, channel, kick_q); return 0; } diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index 42ae6a5a2538..8cf78ed96747 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -1454,6 +1454,7 @@ void hv_event_tasklet_enable(struct vmbus_channel *channel); void hv_process_channel_removal(struct vmbus_channel *channel, u32 relid); +void vmbus_setevent(struct vmbus_channel *channel); /* * Negotiated version with the Host. */ -- cgit v1.2.3-71-gd317 From 3372592a140db69fd63837e81f048ab4abf8111e Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Sun, 6 Nov 2016 13:14:18 -0800 Subject: Drivers: hv: vmbus: On the read path cleanup the logic to interrupt the host Signal the host when we determine the host is to be signaled - on th read path. The currrent code determines the need to signal in the ringbuffer code and actually issues the signal elsewhere. This can result in the host viewing this interrupt as spurious since the host may also poll the channel. Make the necessary adjustments. Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman --- drivers/hv/channel.c | 11 ++--------- drivers/hv/hyperv_vmbus.h | 4 ++-- drivers/hv/ring_buffer.c | 7 ++++--- include/linux/hyperv.h | 12 ++++++------ 4 files changed, 14 insertions(+), 20 deletions(-) (limited to 'include/linux') diff --git a/drivers/hv/channel.c b/drivers/hv/channel.c index 8a8148f7b842..5fb4c6d9209b 100644 --- a/drivers/hv/channel.c +++ b/drivers/hv/channel.c @@ -879,16 +879,9 @@ __vmbus_recvpacket(struct vmbus_channel *channel, void *buffer, u32 bufferlen, u32 *buffer_actual_len, u64 *requestid, bool raw) { - int ret; - bool signal = false; - - ret = hv_ringbuffer_read(&channel->inbound, buffer, bufferlen, - buffer_actual_len, requestid, &signal, raw); + return hv_ringbuffer_read(channel, buffer, bufferlen, + buffer_actual_len, requestid, raw); - if (signal) - vmbus_setevent(channel); - - return ret; } int vmbus_recvpacket(struct vmbus_channel *channel, void *buffer, diff --git a/drivers/hv/hyperv_vmbus.h b/drivers/hv/hyperv_vmbus.h index 2d42ebe2af6c..0675b395ce5c 100644 --- a/drivers/hv/hyperv_vmbus.h +++ b/drivers/hv/hyperv_vmbus.h @@ -532,9 +532,9 @@ int hv_ringbuffer_write(struct vmbus_channel *channel, u32 kv_count, bool lock, bool kick_q); -int hv_ringbuffer_read(struct hv_ring_buffer_info *inring_info, +int hv_ringbuffer_read(struct vmbus_channel *channel, void *buffer, u32 buflen, u32 *buffer_actual_len, - u64 *requestid, bool *signal, bool raw); + u64 *requestid, bool raw); void hv_ringbuffer_get_debuginfo(struct hv_ring_buffer_info *ring_info, struct hv_ring_buffer_debug_info *debug_info); diff --git a/drivers/hv/ring_buffer.c b/drivers/hv/ring_buffer.c index 4af71306d0ff..cd49cb17eb7f 100644 --- a/drivers/hv/ring_buffer.c +++ b/drivers/hv/ring_buffer.c @@ -353,9 +353,9 @@ int hv_ringbuffer_write(struct vmbus_channel *channel, return 0; } -int hv_ringbuffer_read(struct hv_ring_buffer_info *inring_info, +int hv_ringbuffer_read(struct vmbus_channel *channel, void *buffer, u32 buflen, u32 *buffer_actual_len, - u64 *requestid, bool *signal, bool raw) + u64 *requestid, bool raw) { u32 bytes_avail_toread; u32 next_read_location = 0; @@ -364,6 +364,7 @@ int hv_ringbuffer_read(struct hv_ring_buffer_info *inring_info, u32 offset; u32 packetlen; int ret = 0; + struct hv_ring_buffer_info *inring_info = &channel->inbound; if (buflen <= 0) return -EINVAL; @@ -421,7 +422,7 @@ int hv_ringbuffer_read(struct hv_ring_buffer_info *inring_info, /* Update the read index */ hv_set_next_read_location(inring_info, next_read_location); - *signal = hv_need_to_signal_on_read(inring_info); + hv_signal_on_read(channel); return ret; } diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index 8cf78ed96747..fdb0a87323f3 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -1487,10 +1487,11 @@ hv_get_ring_buffer(struct hv_ring_buffer_info *ring_info) * there is room for the producer to send the pending packet. */ -static inline bool hv_need_to_signal_on_read(struct hv_ring_buffer_info *rbi) +static inline void hv_signal_on_read(struct vmbus_channel *channel) { u32 cur_write_sz; u32 pending_sz; + struct hv_ring_buffer_info *rbi = &channel->inbound; /* * Issue a full memory barrier before making the signaling decision. @@ -1508,14 +1509,14 @@ static inline bool hv_need_to_signal_on_read(struct hv_ring_buffer_info *rbi) pending_sz = READ_ONCE(rbi->ring_buffer->pending_send_sz); /* If the other end is not blocked on write don't bother. */ if (pending_sz == 0) - return false; + return; cur_write_sz = hv_get_bytes_to_write(rbi); if (cur_write_sz >= pending_sz) - return true; + vmbus_setevent(channel); - return false; + return; } /* @@ -1587,8 +1588,7 @@ static inline void commit_rd_index(struct vmbus_channel *channel) virt_rmb(); ring_info->ring_buffer->read_index = ring_info->priv_read_index; - if (hv_need_to_signal_on_read(ring_info)) - vmbus_set_event(channel); + hv_signal_on_read(channel); } -- cgit v1.2.3-71-gd317 From 39a842e22c1bf3ec3dce36e01fe8ba8ee66c80c8 Mon Sep 17 00:00:00 2001 From: Alan Tull Date: Tue, 1 Nov 2016 14:14:22 -0500 Subject: of/overlay: add of overlay notifications This patch add of overlay notifications. When DT overlays are being added, some drivers/subsystems need to see device tree overlays before the changes go into the live tree. This is distinct from reconfig notifiers that are post-apply or post-remove and which issue very granular notifications without providing access to the context of a whole overlay. The following 4 notificatons are issued: OF_OVERLAY_PRE_APPLY OF_OVERLAY_POST_APPLY OF_OVERLAY_PRE_REMOVE OF_OVERLAY_POST_REMOVE In the case of pre-apply notification, if the notifier returns error, the overlay will be rejected. This patch exports two functions for registering/unregistering notifications: of_overlay_notifier_register(struct notifier_block *nb) of_overlay_notifier_unregister(struct notifier_block *nb) The of_mutex is held during these notifications. The notification data includes pointers to the overlay target and the overlay: struct of_overlay_notify_data { struct device_node *overlay; struct device_node *target; }; Signed-off-by: Alan Tull Acked-by: Rob Herring Acked-by: Moritz Fischer Signed-off-by: Greg Kroah-Hartman --- drivers/of/overlay.c | 47 ++++++++++++++++++++++++++++++++++++++++++++++- include/linux/of.h | 25 +++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 1 deletion(-) (limited to 'include/linux') diff --git a/drivers/of/overlay.c b/drivers/of/overlay.c index 318dbb51e7a2..0d4cda7050e0 100644 --- a/drivers/of/overlay.c +++ b/drivers/of/overlay.c @@ -58,6 +58,41 @@ struct of_overlay { static int of_overlay_apply_one(struct of_overlay *ov, struct device_node *target, const struct device_node *overlay); +static BLOCKING_NOTIFIER_HEAD(of_overlay_chain); + +int of_overlay_notifier_register(struct notifier_block *nb) +{ + return blocking_notifier_chain_register(&of_overlay_chain, nb); +} +EXPORT_SYMBOL_GPL(of_overlay_notifier_register); + +int of_overlay_notifier_unregister(struct notifier_block *nb) +{ + return blocking_notifier_chain_unregister(&of_overlay_chain, nb); +} +EXPORT_SYMBOL_GPL(of_overlay_notifier_unregister); + +static int of_overlay_notify(struct of_overlay *ov, + enum of_overlay_notify_action action) +{ + struct of_overlay_notify_data nd; + int i, ret; + + for (i = 0; i < ov->count; i++) { + struct of_overlay_info *ovinfo = &ov->ovinfo_tab[i]; + + nd.target = ovinfo->target; + nd.overlay = ovinfo->overlay; + + ret = blocking_notifier_call_chain(&of_overlay_chain, + action, &nd); + if (ret) + return notifier_to_errno(ret); + } + + return 0; +} + static int of_overlay_apply_single_property(struct of_overlay *ov, struct device_node *target, struct property *prop) { @@ -368,6 +403,13 @@ int of_overlay_create(struct device_node *tree) goto err_free_idr; } + err = of_overlay_notify(ov, OF_OVERLAY_PRE_APPLY); + if (err < 0) { + pr_err("%s: Pre-apply notifier failed (err=%d)\n", + __func__, err); + goto err_free_idr; + } + /* apply the overlay */ err = of_overlay_apply(ov); if (err) @@ -382,6 +424,8 @@ int of_overlay_create(struct device_node *tree) /* add to the tail of the overlay list */ list_add_tail(&ov->node, &ov_list); + of_overlay_notify(ov, OF_OVERLAY_POST_APPLY); + mutex_unlock(&of_mutex); return id; @@ -498,9 +542,10 @@ int of_overlay_destroy(int id) goto out; } - + of_overlay_notify(ov, OF_OVERLAY_PRE_REMOVE); list_del(&ov->node); __of_changeset_revert(&ov->cset); + of_overlay_notify(ov, OF_OVERLAY_POST_REMOVE); of_free_overlay_info(ov); idr_remove(&ov_idr, id); of_changeset_destroy(&ov->cset); diff --git a/include/linux/of.h b/include/linux/of.h index 299aeb192727..d72f01009297 100644 --- a/include/linux/of.h +++ b/include/linux/of.h @@ -1266,6 +1266,18 @@ static inline bool of_device_is_system_power_controller(const struct device_node * Overlay support */ +enum of_overlay_notify_action { + OF_OVERLAY_PRE_APPLY, + OF_OVERLAY_POST_APPLY, + OF_OVERLAY_PRE_REMOVE, + OF_OVERLAY_POST_REMOVE, +}; + +struct of_overlay_notify_data { + struct device_node *overlay; + struct device_node *target; +}; + #ifdef CONFIG_OF_OVERLAY /* ID based overlays; the API for external users */ @@ -1273,6 +1285,9 @@ int of_overlay_create(struct device_node *tree); int of_overlay_destroy(int id); int of_overlay_destroy_all(void); +int of_overlay_notifier_register(struct notifier_block *nb); +int of_overlay_notifier_unregister(struct notifier_block *nb); + #else static inline int of_overlay_create(struct device_node *tree) @@ -1290,6 +1305,16 @@ static inline int of_overlay_destroy_all(void) return -ENOTSUPP; } +static inline int of_overlay_notifier_register(struct notifier_block *nb) +{ + return 0; +} + +static inline int of_overlay_notifier_unregister(struct notifier_block *nb) +{ + return 0; +} + #endif #endif /* _LINUX_OF_H */ -- cgit v1.2.3-71-gd317 From 9dce0287a60d72656a787b075f1b9162ff3cb142 Mon Sep 17 00:00:00 2001 From: Alan Tull Date: Tue, 1 Nov 2016 14:14:23 -0500 Subject: fpga: add method to get fpga manager from device The intent is to provide a non-DT method of getting ahold of a FPGA manager to do some FPGA programming. This patch refactors of_fpga_mgr_get() to reuse most of it while adding a new method fpga_mgr_get() for getting a pointer to a fpga manager struct, given the device. Signed-off-by: Alan Tull Signed-off-by: Greg Kroah-Hartman --- Documentation/fpga/fpga-mgr.txt | 6 ++-- drivers/fpga/fpga-mgr.c | 76 +++++++++++++++++++++++++++++------------ include/linux/fpga/fpga-mgr.h | 2 ++ 3 files changed, 60 insertions(+), 24 deletions(-) (limited to 'include/linux') diff --git a/Documentation/fpga/fpga-mgr.txt b/Documentation/fpga/fpga-mgr.txt index ce3e84fa9023..d056d691e8fd 100644 --- a/Documentation/fpga/fpga-mgr.txt +++ b/Documentation/fpga/fpga-mgr.txt @@ -38,11 +38,13 @@ To get/put a reference to a FPGA manager: ----------------------------------------- struct fpga_manager *of_fpga_mgr_get(struct device_node *node); + struct fpga_manager *fpga_mgr_get(struct device *dev); + +Given a DT node or device, get an exclusive reference to a FPGA manager. void fpga_mgr_put(struct fpga_manager *mgr); -Given a DT node, get an exclusive reference to a FPGA manager or release -the reference. +Release the reference. To register or unregister the low level FPGA-specific driver: diff --git a/drivers/fpga/fpga-mgr.c b/drivers/fpga/fpga-mgr.c index 953dc9195937..b690e65d55fe 100644 --- a/drivers/fpga/fpga-mgr.c +++ b/drivers/fpga/fpga-mgr.c @@ -39,7 +39,8 @@ static struct class *fpga_mgr_class; * Step the low level fpga manager through the device-specific steps of getting * an FPGA ready to be configured, writing the image to it, then doing whatever * post-configuration steps necessary. This code assumes the caller got the - * mgr pointer from of_fpga_mgr_get() and checked that it is not an error code. + * mgr pointer from of_fpga_mgr_get() or fpga_mgr_get() and checked that it is + * not an error code. * * Return: 0 on success, negative error code otherwise. */ @@ -99,7 +100,8 @@ EXPORT_SYMBOL_GPL(fpga_mgr_buf_load); * Request an FPGA image using the firmware class, then write out to the FPGA. * Update the state before each step to provide info on what step failed if * there is a failure. This code assumes the caller got the mgr pointer - * from of_fpga_mgr_get() and checked that it is not an error code. + * from of_fpga_mgr_get() or fpga_mgr_get() and checked that it is not an error + * code. * * Return: 0 on success, negative error code otherwise. */ @@ -181,30 +183,11 @@ static struct attribute *fpga_mgr_attrs[] = { }; ATTRIBUTE_GROUPS(fpga_mgr); -static int fpga_mgr_of_node_match(struct device *dev, const void *data) -{ - return dev->of_node == data; -} - -/** - * of_fpga_mgr_get - get an exclusive reference to a fpga mgr - * @node: device node - * - * Given a device node, get an exclusive reference to a fpga mgr. - * - * Return: fpga manager struct or IS_ERR() condition containing error code. - */ -struct fpga_manager *of_fpga_mgr_get(struct device_node *node) +struct fpga_manager *__fpga_mgr_get(struct device *dev) { struct fpga_manager *mgr; - struct device *dev; int ret = -ENODEV; - dev = class_find_device(fpga_mgr_class, NULL, node, - fpga_mgr_of_node_match); - if (!dev) - return ERR_PTR(-ENODEV); - mgr = to_fpga_manager(dev); if (!mgr) goto err_dev; @@ -226,6 +209,55 @@ err_dev: put_device(dev); return ERR_PTR(ret); } + +static int fpga_mgr_dev_match(struct device *dev, const void *data) +{ + return dev->parent == data; +} + +/** + * fpga_mgr_get - get an exclusive reference to a fpga mgr + * @dev: parent device that fpga mgr was registered with + * + * Given a device, get an exclusive reference to a fpga mgr. + * + * Return: fpga manager struct or IS_ERR() condition containing error code. + */ +struct fpga_manager *fpga_mgr_get(struct device *dev) +{ + struct device *mgr_dev = class_find_device(fpga_mgr_class, NULL, dev, + fpga_mgr_dev_match); + if (!mgr_dev) + return ERR_PTR(-ENODEV); + + return __fpga_mgr_get(mgr_dev); +} +EXPORT_SYMBOL_GPL(fpga_mgr_get); + +static int fpga_mgr_of_node_match(struct device *dev, const void *data) +{ + return dev->of_node == data; +} + +/** + * of_fpga_mgr_get - get an exclusive reference to a fpga mgr + * @node: device node + * + * Given a device node, get an exclusive reference to a fpga mgr. + * + * Return: fpga manager struct or IS_ERR() condition containing error code. + */ +struct fpga_manager *of_fpga_mgr_get(struct device_node *node) +{ + struct device *dev; + + dev = class_find_device(fpga_mgr_class, NULL, node, + fpga_mgr_of_node_match); + if (!dev) + return ERR_PTR(-ENODEV); + + return __fpga_mgr_get(dev); +} EXPORT_SYMBOL_GPL(of_fpga_mgr_get); /** diff --git a/include/linux/fpga/fpga-mgr.h b/include/linux/fpga/fpga-mgr.h index 0940bf45e2f2..957b5ac9428a 100644 --- a/include/linux/fpga/fpga-mgr.h +++ b/include/linux/fpga/fpga-mgr.h @@ -117,6 +117,8 @@ int fpga_mgr_firmware_load(struct fpga_manager *mgr, u32 flags, struct fpga_manager *of_fpga_mgr_get(struct device_node *node); +struct fpga_manager *fpga_mgr_get(struct device *dev); + void fpga_mgr_put(struct fpga_manager *mgr); int fpga_mgr_register(struct device *dev, const char *name, -- cgit v1.2.3-71-gd317 From 1df2865f8dd9d56cb76aa7aa1298921e7bece2af Mon Sep 17 00:00:00 2001 From: Alan Tull Date: Tue, 1 Nov 2016 14:14:26 -0500 Subject: fpga-mgr: add fpga image information struct This patch adds a minor change in the FPGA Manager API to hold information that is specific to an FPGA image file. This change is expected to bring little, if any, pain. The socfpga and zynq drivers are fixed up in this patch. An FPGA image file will have particulars that affect how the image is programmed to the FPGA. One example is that current 'flags' currently has one bit which shows whether the FPGA image was built for full reconfiguration or partial reconfiguration. Another example is timeout values for enabling or disabling the bridges in the FPGA. As the complexity of the FPGA design increases, the bridges in the FPGA may take longer times to enable or disable. This patch adds a new 'struct fpga_image_info', moves the current 'u32 flags' to it. Two other image-specific u32's are added for the bridge enable/disable timeouts. The FPGA Manager API functions are changed, replacing the 'u32 flag' parameter with a pointer to struct fpga_image_info. Subsequent patches fix the existing low level FPGA manager drivers. Signed-off-by: Alan Tull Acked-by: Moritz Fischer Signed-off-by: Greg Kroah-Hartman --- drivers/fpga/fpga-mgr.c | 17 +++++++++-------- drivers/fpga/socfpga.c | 7 ++++--- drivers/fpga/zynq-fpga.c | 10 ++++++---- include/linux/fpga/fpga-mgr.h | 23 +++++++++++++++++++---- 4 files changed, 38 insertions(+), 19 deletions(-) (limited to 'include/linux') diff --git a/drivers/fpga/fpga-mgr.c b/drivers/fpga/fpga-mgr.c index b690e65d55fe..79ce2eea44db 100644 --- a/drivers/fpga/fpga-mgr.c +++ b/drivers/fpga/fpga-mgr.c @@ -32,7 +32,7 @@ static struct class *fpga_mgr_class; /** * fpga_mgr_buf_load - load fpga from image in buffer * @mgr: fpga manager - * @flags: flags setting fpga confuration modes + * @info: fpga image specific information * @buf: buffer contain fpga image * @count: byte count of buf * @@ -44,8 +44,8 @@ static struct class *fpga_mgr_class; * * Return: 0 on success, negative error code otherwise. */ -int fpga_mgr_buf_load(struct fpga_manager *mgr, u32 flags, const char *buf, - size_t count) +int fpga_mgr_buf_load(struct fpga_manager *mgr, struct fpga_image_info *info, + const char *buf, size_t count) { struct device *dev = &mgr->dev; int ret; @@ -56,7 +56,7 @@ int fpga_mgr_buf_load(struct fpga_manager *mgr, u32 flags, const char *buf, * ready to receive an FPGA image. */ mgr->state = FPGA_MGR_STATE_WRITE_INIT; - ret = mgr->mops->write_init(mgr, flags, buf, count); + ret = mgr->mops->write_init(mgr, info, buf, count); if (ret) { dev_err(dev, "Error preparing FPGA for writing\n"); mgr->state = FPGA_MGR_STATE_WRITE_INIT_ERR; @@ -79,7 +79,7 @@ int fpga_mgr_buf_load(struct fpga_manager *mgr, u32 flags, const char *buf, * steps to finish and set the FPGA into operating mode. */ mgr->state = FPGA_MGR_STATE_WRITE_COMPLETE; - ret = mgr->mops->write_complete(mgr, flags); + ret = mgr->mops->write_complete(mgr, info); if (ret) { dev_err(dev, "Error after writing image data to FPGA\n"); mgr->state = FPGA_MGR_STATE_WRITE_COMPLETE_ERR; @@ -94,7 +94,7 @@ EXPORT_SYMBOL_GPL(fpga_mgr_buf_load); /** * fpga_mgr_firmware_load - request firmware and load to fpga * @mgr: fpga manager - * @flags: flags setting fpga confuration modes + * @info: fpga image specific information * @image_name: name of image file on the firmware search path * * Request an FPGA image using the firmware class, then write out to the FPGA. @@ -105,7 +105,8 @@ EXPORT_SYMBOL_GPL(fpga_mgr_buf_load); * * Return: 0 on success, negative error code otherwise. */ -int fpga_mgr_firmware_load(struct fpga_manager *mgr, u32 flags, +int fpga_mgr_firmware_load(struct fpga_manager *mgr, + struct fpga_image_info *info, const char *image_name) { struct device *dev = &mgr->dev; @@ -123,7 +124,7 @@ int fpga_mgr_firmware_load(struct fpga_manager *mgr, u32 flags, return ret; } - ret = fpga_mgr_buf_load(mgr, flags, fw->data, fw->size); + ret = fpga_mgr_buf_load(mgr, info, fw->data, fw->size); release_firmware(fw); diff --git a/drivers/fpga/socfpga.c b/drivers/fpga/socfpga.c index 27d2ff28132c..b6672e66cda6 100644 --- a/drivers/fpga/socfpga.c +++ b/drivers/fpga/socfpga.c @@ -407,13 +407,14 @@ static int socfpga_fpga_reset(struct fpga_manager *mgr) /* * Prepare the FPGA to receive the configuration data. */ -static int socfpga_fpga_ops_configure_init(struct fpga_manager *mgr, u32 flags, +static int socfpga_fpga_ops_configure_init(struct fpga_manager *mgr, + struct fpga_image_info *info, const char *buf, size_t count) { struct socfpga_fpga_priv *priv = mgr->priv; int ret; - if (flags & FPGA_MGR_PARTIAL_RECONFIG) { + if (info->flags & FPGA_MGR_PARTIAL_RECONFIG) { dev_err(&mgr->dev, "Partial reconfiguration not supported.\n"); return -EINVAL; } @@ -478,7 +479,7 @@ static int socfpga_fpga_ops_configure_write(struct fpga_manager *mgr, } static int socfpga_fpga_ops_configure_complete(struct fpga_manager *mgr, - u32 flags) + struct fpga_image_info *info) { struct socfpga_fpga_priv *priv = mgr->priv; u32 status; diff --git a/drivers/fpga/zynq-fpga.c b/drivers/fpga/zynq-fpga.c index c2fb4120bd62..249682e92502 100644 --- a/drivers/fpga/zynq-fpga.c +++ b/drivers/fpga/zynq-fpga.c @@ -175,7 +175,8 @@ static irqreturn_t zynq_fpga_isr(int irq, void *data) return IRQ_HANDLED; } -static int zynq_fpga_ops_write_init(struct fpga_manager *mgr, u32 flags, +static int zynq_fpga_ops_write_init(struct fpga_manager *mgr, + struct fpga_image_info *info, const char *buf, size_t count) { struct zynq_fpga_priv *priv; @@ -189,7 +190,7 @@ static int zynq_fpga_ops_write_init(struct fpga_manager *mgr, u32 flags, return err; /* don't globally reset PL if we're doing partial reconfig */ - if (!(flags & FPGA_MGR_PARTIAL_RECONFIG)) { + if (!(info->flags & FPGA_MGR_PARTIAL_RECONFIG)) { /* assert AXI interface resets */ regmap_write(priv->slcr, SLCR_FPGA_RST_CTRL_OFFSET, FPGA_RST_ALL_MASK); @@ -343,7 +344,8 @@ out_free: return err; } -static int zynq_fpga_ops_write_complete(struct fpga_manager *mgr, u32 flags) +static int zynq_fpga_ops_write_complete(struct fpga_manager *mgr, + struct fpga_image_info *info) { struct zynq_fpga_priv *priv = mgr->priv; int err; @@ -364,7 +366,7 @@ static int zynq_fpga_ops_write_complete(struct fpga_manager *mgr, u32 flags) return err; /* for the partial reconfig case we didn't touch the level shifters */ - if (!(flags & FPGA_MGR_PARTIAL_RECONFIG)) { + if (!(info->flags & FPGA_MGR_PARTIAL_RECONFIG)) { /* enable level shifters from PL to PS */ regmap_write(priv->slcr, SLCR_LVL_SHFTR_EN_OFFSET, LVL_SHFTR_ENABLE_PL_TO_PS); diff --git a/include/linux/fpga/fpga-mgr.h b/include/linux/fpga/fpga-mgr.h index 957b5ac9428a..55803186e0ea 100644 --- a/include/linux/fpga/fpga-mgr.h +++ b/include/linux/fpga/fpga-mgr.h @@ -68,6 +68,18 @@ enum fpga_mgr_states { */ #define FPGA_MGR_PARTIAL_RECONFIG BIT(0) +/** + * struct fpga_image_info - information specific to a FPGA image + * @flags: boolean flags as defined above + * @enable_timeout_us: maximum time to enable traffic through bridge (uSec) + * @disable_timeout_us: maximum time to disable traffic through bridge (uSec) + */ +struct fpga_image_info { + u32 flags; + u32 enable_timeout_us; + u32 disable_timeout_us; +}; + /** * struct fpga_manager_ops - ops for low level fpga manager drivers * @state: returns an enum value of the FPGA's state @@ -82,10 +94,12 @@ enum fpga_mgr_states { */ struct fpga_manager_ops { enum fpga_mgr_states (*state)(struct fpga_manager *mgr); - int (*write_init)(struct fpga_manager *mgr, u32 flags, + int (*write_init)(struct fpga_manager *mgr, + struct fpga_image_info *info, const char *buf, size_t count); int (*write)(struct fpga_manager *mgr, const char *buf, size_t count); - int (*write_complete)(struct fpga_manager *mgr, u32 flags); + int (*write_complete)(struct fpga_manager *mgr, + struct fpga_image_info *info); void (*fpga_remove)(struct fpga_manager *mgr); }; @@ -109,10 +123,11 @@ struct fpga_manager { #define to_fpga_manager(d) container_of(d, struct fpga_manager, dev) -int fpga_mgr_buf_load(struct fpga_manager *mgr, u32 flags, +int fpga_mgr_buf_load(struct fpga_manager *mgr, struct fpga_image_info *info, const char *buf, size_t count); -int fpga_mgr_firmware_load(struct fpga_manager *mgr, u32 flags, +int fpga_mgr_firmware_load(struct fpga_manager *mgr, + struct fpga_image_info *info, const char *image_name); struct fpga_manager *of_fpga_mgr_get(struct device_node *node); -- cgit v1.2.3-71-gd317 From 21aeda950c5f84a8351b862816d832120b217a9b Mon Sep 17 00:00:00 2001 From: Alan Tull Date: Tue, 1 Nov 2016 14:14:28 -0500 Subject: fpga: add fpga bridge framework This framework adds API functions for enabling/ disabling FPGA bridges under kernel control. This allows the Linux kernel to disable FPGA bridges during FPGA reprogramming and to enable FPGA bridges when FPGA reprogramming is done. This framework is be manufacturer-agnostic, allowing it to be used in interfaces that use the FPGA Manager Framework to reprogram FPGA's. The functions are: * of_fpga_bridge_get * fpga_bridge_put Get/put an exclusive reference to a FPGA bridge. * fpga_bridge_enable * fpga_bridge_disable Enable/Disable traffic through a bridge. * fpga_bridge_register * fpga_bridge_unregister Register/unregister a device-specific low level FPGA Bridge driver. Get an exclusive reference to a bridge and add it to a list: * fpga_bridge_get_to_list To enable/disable/put a set of bridges that are on a list: * fpga_bridges_enable * fpga_bridges_disable * fpga_bridges_put Signed-off-by: Alan Tull Signed-off-by: Greg Kroah-Hartman --- drivers/fpga/Kconfig | 7 + drivers/fpga/Makefile | 3 + drivers/fpga/fpga-bridge.c | 395 +++++++++++++++++++++++++++++++++++++++ include/linux/fpga/fpga-bridge.h | 60 ++++++ 4 files changed, 465 insertions(+) create mode 100644 drivers/fpga/fpga-bridge.c create mode 100644 include/linux/fpga/fpga-bridge.h (limited to 'include/linux') diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig index cd84934774cc..9b20f45c85bf 100644 --- a/drivers/fpga/Kconfig +++ b/drivers/fpga/Kconfig @@ -26,6 +26,13 @@ config FPGA_MGR_ZYNQ_FPGA help FPGA manager driver support for Xilinx Zynq FPGAs. +config FPGA_BRIDGE + tristate "FPGA Bridge Framework" + depends on OF + help + Say Y here if you want to support bridges connected between host + processors and FPGAs or between FPGAs. + endif # FPGA endmenu diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile index 8d83fc6b1613..4baef0022d4c 100644 --- a/drivers/fpga/Makefile +++ b/drivers/fpga/Makefile @@ -8,3 +8,6 @@ obj-$(CONFIG_FPGA) += fpga-mgr.o # FPGA Manager Drivers obj-$(CONFIG_FPGA_MGR_SOCFPGA) += socfpga.o obj-$(CONFIG_FPGA_MGR_ZYNQ_FPGA) += zynq-fpga.o + +# FPGA Bridge Drivers +obj-$(CONFIG_FPGA_BRIDGE) += fpga-bridge.o diff --git a/drivers/fpga/fpga-bridge.c b/drivers/fpga/fpga-bridge.c new file mode 100644 index 000000000000..33ee83e6373c --- /dev/null +++ b/drivers/fpga/fpga-bridge.c @@ -0,0 +1,395 @@ +/* + * FPGA Bridge Framework Driver + * + * Copyright (C) 2013-2016 Altera Corporation, All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ +#include +#include +#include +#include +#include +#include +#include + +static DEFINE_IDA(fpga_bridge_ida); +static struct class *fpga_bridge_class; + +/* Lock for adding/removing bridges to linked lists*/ +spinlock_t bridge_list_lock; + +static int fpga_bridge_of_node_match(struct device *dev, const void *data) +{ + return dev->of_node == data; +} + +/** + * fpga_bridge_enable - Enable transactions on the bridge + * + * @bridge: FPGA bridge + * + * Return: 0 for success, error code otherwise. + */ +int fpga_bridge_enable(struct fpga_bridge *bridge) +{ + dev_dbg(&bridge->dev, "enable\n"); + + if (bridge->br_ops && bridge->br_ops->enable_set) + return bridge->br_ops->enable_set(bridge, 1); + + return 0; +} +EXPORT_SYMBOL_GPL(fpga_bridge_enable); + +/** + * fpga_bridge_disable - Disable transactions on the bridge + * + * @bridge: FPGA bridge + * + * Return: 0 for success, error code otherwise. + */ +int fpga_bridge_disable(struct fpga_bridge *bridge) +{ + dev_dbg(&bridge->dev, "disable\n"); + + if (bridge->br_ops && bridge->br_ops->enable_set) + return bridge->br_ops->enable_set(bridge, 0); + + return 0; +} +EXPORT_SYMBOL_GPL(fpga_bridge_disable); + +/** + * of_fpga_bridge_get - get an exclusive reference to a fpga bridge + * + * @np: node pointer of a FPGA bridge + * @info: fpga image specific information + * + * Return fpga_bridge struct if successful. + * Return -EBUSY if someone already has a reference to the bridge. + * Return -ENODEV if @np is not a FPGA Bridge. + */ +struct fpga_bridge *of_fpga_bridge_get(struct device_node *np, + struct fpga_image_info *info) + +{ + struct device *dev; + struct fpga_bridge *bridge; + int ret = -ENODEV; + + dev = class_find_device(fpga_bridge_class, NULL, np, + fpga_bridge_of_node_match); + if (!dev) + goto err_dev; + + bridge = to_fpga_bridge(dev); + if (!bridge) + goto err_dev; + + bridge->info = info; + + if (!mutex_trylock(&bridge->mutex)) { + ret = -EBUSY; + goto err_dev; + } + + if (!try_module_get(dev->parent->driver->owner)) + goto err_ll_mod; + + dev_dbg(&bridge->dev, "get\n"); + + return bridge; + +err_ll_mod: + mutex_unlock(&bridge->mutex); +err_dev: + put_device(dev); + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(of_fpga_bridge_get); + +/** + * fpga_bridge_put - release a reference to a bridge + * + * @bridge: FPGA bridge + */ +void fpga_bridge_put(struct fpga_bridge *bridge) +{ + dev_dbg(&bridge->dev, "put\n"); + + bridge->info = NULL; + module_put(bridge->dev.parent->driver->owner); + mutex_unlock(&bridge->mutex); + put_device(&bridge->dev); +} +EXPORT_SYMBOL_GPL(fpga_bridge_put); + +/** + * fpga_bridges_enable - enable bridges in a list + * @bridge_list: list of FPGA bridges + * + * Enable each bridge in the list. If list is empty, do nothing. + * + * Return 0 for success or empty bridge list; return error code otherwise. + */ +int fpga_bridges_enable(struct list_head *bridge_list) +{ + struct fpga_bridge *bridge; + struct list_head *node; + int ret; + + list_for_each(node, bridge_list) { + bridge = list_entry(node, struct fpga_bridge, node); + ret = fpga_bridge_enable(bridge); + if (ret) + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(fpga_bridges_enable); + +/** + * fpga_bridges_disable - disable bridges in a list + * + * @bridge_list: list of FPGA bridges + * + * Disable each bridge in the list. If list is empty, do nothing. + * + * Return 0 for success or empty bridge list; return error code otherwise. + */ +int fpga_bridges_disable(struct list_head *bridge_list) +{ + struct fpga_bridge *bridge; + struct list_head *node; + int ret; + + list_for_each(node, bridge_list) { + bridge = list_entry(node, struct fpga_bridge, node); + ret = fpga_bridge_disable(bridge); + if (ret) + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(fpga_bridges_disable); + +/** + * fpga_bridges_put - put bridges + * + * @bridge_list: list of FPGA bridges + * + * For each bridge in the list, put the bridge and remove it from the list. + * If list is empty, do nothing. + */ +void fpga_bridges_put(struct list_head *bridge_list) +{ + struct fpga_bridge *bridge; + struct list_head *node, *next; + unsigned long flags; + + list_for_each_safe(node, next, bridge_list) { + bridge = list_entry(node, struct fpga_bridge, node); + + fpga_bridge_put(bridge); + + spin_lock_irqsave(&bridge_list_lock, flags); + list_del(&bridge->node); + spin_unlock_irqrestore(&bridge_list_lock, flags); + } +} +EXPORT_SYMBOL_GPL(fpga_bridges_put); + +/** + * fpga_bridges_get_to_list - get a bridge, add it to a list + * + * @np: node pointer of a FPGA bridge + * @info: fpga image specific information + * @bridge_list: list of FPGA bridges + * + * Get an exclusive reference to the bridge and and it to the list. + * + * Return 0 for success, error code from of_fpga_bridge_get() othewise. + */ +int fpga_bridge_get_to_list(struct device_node *np, + struct fpga_image_info *info, + struct list_head *bridge_list) +{ + struct fpga_bridge *bridge; + unsigned long flags; + + bridge = of_fpga_bridge_get(np, info); + if (IS_ERR(bridge)) + return PTR_ERR(bridge); + + spin_lock_irqsave(&bridge_list_lock, flags); + list_add(&bridge->node, bridge_list); + spin_unlock_irqrestore(&bridge_list_lock, flags); + + return 0; +} +EXPORT_SYMBOL_GPL(fpga_bridge_get_to_list); + +static ssize_t name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fpga_bridge *bridge = to_fpga_bridge(dev); + + return sprintf(buf, "%s\n", bridge->name); +} + +static ssize_t state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fpga_bridge *bridge = to_fpga_bridge(dev); + int enable = 1; + + if (bridge->br_ops && bridge->br_ops->enable_show) + enable = bridge->br_ops->enable_show(bridge); + + return sprintf(buf, "%s\n", enable ? "enabled" : "disabled"); +} + +static DEVICE_ATTR_RO(name); +static DEVICE_ATTR_RO(state); + +static struct attribute *fpga_bridge_attrs[] = { + &dev_attr_name.attr, + &dev_attr_state.attr, + NULL, +}; +ATTRIBUTE_GROUPS(fpga_bridge); + +/** + * fpga_bridge_register - register a fpga bridge driver + * @dev: FPGA bridge device from pdev + * @name: FPGA bridge name + * @br_ops: pointer to structure of fpga bridge ops + * @priv: FPGA bridge private data + * + * Return: 0 for success, error code otherwise. + */ +int fpga_bridge_register(struct device *dev, const char *name, + const struct fpga_bridge_ops *br_ops, void *priv) +{ + struct fpga_bridge *bridge; + int id, ret = 0; + + if (!name || !strlen(name)) { + dev_err(dev, "Attempt to register with no name!\n"); + return -EINVAL; + } + + bridge = kzalloc(sizeof(*bridge), GFP_KERNEL); + if (!bridge) + return -ENOMEM; + + id = ida_simple_get(&fpga_bridge_ida, 0, 0, GFP_KERNEL); + if (id < 0) { + ret = id; + goto error_kfree; + } + + mutex_init(&bridge->mutex); + INIT_LIST_HEAD(&bridge->node); + + bridge->name = name; + bridge->br_ops = br_ops; + bridge->priv = priv; + + device_initialize(&bridge->dev); + bridge->dev.class = fpga_bridge_class; + bridge->dev.parent = dev; + bridge->dev.of_node = dev->of_node; + bridge->dev.id = id; + dev_set_drvdata(dev, bridge); + + ret = dev_set_name(&bridge->dev, "br%d", id); + if (ret) + goto error_device; + + ret = device_add(&bridge->dev); + if (ret) + goto error_device; + + of_platform_populate(dev->of_node, NULL, NULL, dev); + + dev_info(bridge->dev.parent, "fpga bridge [%s] registered\n", + bridge->name); + + return 0; + +error_device: + ida_simple_remove(&fpga_bridge_ida, id); +error_kfree: + kfree(bridge); + + return ret; +} +EXPORT_SYMBOL_GPL(fpga_bridge_register); + +/** + * fpga_bridge_unregister - unregister a fpga bridge driver + * @dev: FPGA bridge device from pdev + */ +void fpga_bridge_unregister(struct device *dev) +{ + struct fpga_bridge *bridge = dev_get_drvdata(dev); + + /* + * If the low level driver provides a method for putting bridge into + * a desired state upon unregister, do it. + */ + if (bridge->br_ops && bridge->br_ops->fpga_bridge_remove) + bridge->br_ops->fpga_bridge_remove(bridge); + + device_unregister(&bridge->dev); +} +EXPORT_SYMBOL_GPL(fpga_bridge_unregister); + +static void fpga_bridge_dev_release(struct device *dev) +{ + struct fpga_bridge *bridge = to_fpga_bridge(dev); + + ida_simple_remove(&fpga_bridge_ida, bridge->dev.id); + kfree(bridge); +} + +static int __init fpga_bridge_dev_init(void) +{ + spin_lock_init(&bridge_list_lock); + + fpga_bridge_class = class_create(THIS_MODULE, "fpga_bridge"); + if (IS_ERR(fpga_bridge_class)) + return PTR_ERR(fpga_bridge_class); + + fpga_bridge_class->dev_groups = fpga_bridge_groups; + fpga_bridge_class->dev_release = fpga_bridge_dev_release; + + return 0; +} + +static void __exit fpga_bridge_dev_exit(void) +{ + class_destroy(fpga_bridge_class); + ida_destroy(&fpga_bridge_ida); +} + +MODULE_DESCRIPTION("FPGA Bridge Driver"); +MODULE_AUTHOR("Alan Tull "); +MODULE_LICENSE("GPL v2"); + +subsys_initcall(fpga_bridge_dev_init); +module_exit(fpga_bridge_dev_exit); diff --git a/include/linux/fpga/fpga-bridge.h b/include/linux/fpga/fpga-bridge.h new file mode 100644 index 000000000000..dba6e3c697c7 --- /dev/null +++ b/include/linux/fpga/fpga-bridge.h @@ -0,0 +1,60 @@ +#include +#include + +#ifndef _LINUX_FPGA_BRIDGE_H +#define _LINUX_FPGA_BRIDGE_H + +struct fpga_bridge; + +/** + * struct fpga_bridge_ops - ops for low level FPGA bridge drivers + * @enable_show: returns the FPGA bridge's status + * @enable_set: set a FPGA bridge as enabled or disabled + * @fpga_bridge_remove: set FPGA into a specific state during driver remove + */ +struct fpga_bridge_ops { + int (*enable_show)(struct fpga_bridge *bridge); + int (*enable_set)(struct fpga_bridge *bridge, bool enable); + void (*fpga_bridge_remove)(struct fpga_bridge *bridge); +}; + +/** + * struct fpga_bridge - FPGA bridge structure + * @name: name of low level FPGA bridge + * @dev: FPGA bridge device + * @mutex: enforces exclusive reference to bridge + * @br_ops: pointer to struct of FPGA bridge ops + * @info: fpga image specific information + * @node: FPGA bridge list node + * @priv: low level driver private date + */ +struct fpga_bridge { + const char *name; + struct device dev; + struct mutex mutex; /* for exclusive reference to bridge */ + const struct fpga_bridge_ops *br_ops; + struct fpga_image_info *info; + struct list_head node; + void *priv; +}; + +#define to_fpga_bridge(d) container_of(d, struct fpga_bridge, dev) + +struct fpga_bridge *of_fpga_bridge_get(struct device_node *node, + struct fpga_image_info *info); +void fpga_bridge_put(struct fpga_bridge *bridge); +int fpga_bridge_enable(struct fpga_bridge *bridge); +int fpga_bridge_disable(struct fpga_bridge *bridge); + +int fpga_bridges_enable(struct list_head *bridge_list); +int fpga_bridges_disable(struct list_head *bridge_list); +void fpga_bridges_put(struct list_head *bridge_list); +int fpga_bridge_get_to_list(struct device_node *np, + struct fpga_image_info *info, + struct list_head *bridge_list); + +int fpga_bridge_register(struct device *dev, const char *name, + const struct fpga_bridge_ops *br_ops, void *priv); +void fpga_bridge_unregister(struct device *dev); + +#endif /* _LINUX_FPGA_BRIDGE_H */ -- cgit v1.2.3-71-gd317 From 0fa20cdfcc1f68847cdfc47824476301eedc8297 Mon Sep 17 00:00:00 2001 From: Alan Tull Date: Tue, 1 Nov 2016 14:14:29 -0500 Subject: fpga: fpga-region: device tree control for FPGA FPGA Regions support programming FPGA under control of the Device Tree. Signed-off-by: Alan Tull Signed-off-by: Greg Kroah-Hartman --- drivers/fpga/Kconfig | 7 + drivers/fpga/Makefile | 3 + drivers/fpga/fpga-region.c | 603 ++++++++++++++++++++++++++++++++++++++++++ include/linux/fpga/fpga-mgr.h | 2 + 4 files changed, 615 insertions(+) create mode 100644 drivers/fpga/fpga-region.c (limited to 'include/linux') diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig index 9b20f45c85bf..e0e1257e17e2 100644 --- a/drivers/fpga/Kconfig +++ b/drivers/fpga/Kconfig @@ -13,6 +13,13 @@ config FPGA if FPGA +config FPGA_REGION + tristate "FPGA Region" + depends on OF && FPGA_BRIDGE + help + FPGA Regions allow loading FPGA images under control of + the Device Tree. + config FPGA_MGR_SOCFPGA tristate "Altera SOCFPGA FPGA Manager" depends on ARCH_SOCFPGA diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile index 4baef0022d4c..8d746c342533 100644 --- a/drivers/fpga/Makefile +++ b/drivers/fpga/Makefile @@ -11,3 +11,6 @@ obj-$(CONFIG_FPGA_MGR_ZYNQ_FPGA) += zynq-fpga.o # FPGA Bridge Drivers obj-$(CONFIG_FPGA_BRIDGE) += fpga-bridge.o + +# High Level Interfaces +obj-$(CONFIG_FPGA_REGION) += fpga-region.o diff --git a/drivers/fpga/fpga-region.c b/drivers/fpga/fpga-region.c new file mode 100644 index 000000000000..3222fdbad75a --- /dev/null +++ b/drivers/fpga/fpga-region.c @@ -0,0 +1,603 @@ +/* + * FPGA Region - Device Tree support for FPGA programming under Linux + * + * Copyright (C) 2013-2016 Altera Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * struct fpga_region - FPGA Region structure + * @dev: FPGA Region device + * @mutex: enforces exclusive reference to region + * @bridge_list: list of FPGA bridges specified in region + * @info: fpga image specific information + */ +struct fpga_region { + struct device dev; + struct mutex mutex; /* for exclusive reference to region */ + struct list_head bridge_list; + struct fpga_image_info *info; +}; + +#define to_fpga_region(d) container_of(d, struct fpga_region, dev) + +static DEFINE_IDA(fpga_region_ida); +static struct class *fpga_region_class; + +static const struct of_device_id fpga_region_of_match[] = { + { .compatible = "fpga-region", }, + {}, +}; +MODULE_DEVICE_TABLE(of, fpga_region_of_match); + +static int fpga_region_of_node_match(struct device *dev, const void *data) +{ + return dev->of_node == data; +} + +/** + * fpga_region_find - find FPGA region + * @np: device node of FPGA Region + * Caller will need to put_device(®ion->dev) when done. + * Returns FPGA Region struct or NULL + */ +static struct fpga_region *fpga_region_find(struct device_node *np) +{ + struct device *dev; + + dev = class_find_device(fpga_region_class, NULL, np, + fpga_region_of_node_match); + if (!dev) + return NULL; + + return to_fpga_region(dev); +} + +/** + * fpga_region_get - get an exclusive reference to a fpga region + * @region: FPGA Region struct + * + * Caller should call fpga_region_put() when done with region. + * + * Return fpga_region struct if successful. + * Return -EBUSY if someone already has a reference to the region. + * Return -ENODEV if @np is not a FPGA Region. + */ +static struct fpga_region *fpga_region_get(struct fpga_region *region) +{ + struct device *dev = ®ion->dev; + + if (!mutex_trylock(®ion->mutex)) { + dev_dbg(dev, "%s: FPGA Region already in use\n", __func__); + return ERR_PTR(-EBUSY); + } + + get_device(dev); + of_node_get(dev->of_node); + if (!try_module_get(dev->parent->driver->owner)) { + of_node_put(dev->of_node); + put_device(dev); + mutex_unlock(®ion->mutex); + return ERR_PTR(-ENODEV); + } + + dev_dbg(®ion->dev, "get\n"); + + return region; +} + +/** + * fpga_region_put - release a reference to a region + * + * @region: FPGA region + */ +static void fpga_region_put(struct fpga_region *region) +{ + struct device *dev = ®ion->dev; + + dev_dbg(®ion->dev, "put\n"); + + module_put(dev->parent->driver->owner); + of_node_put(dev->of_node); + put_device(dev); + mutex_unlock(®ion->mutex); +} + +/** + * fpga_region_get_manager - get exclusive reference for FPGA manager + * @region: FPGA region + * + * Get FPGA Manager from "fpga-mgr" property or from ancestor region. + * + * Caller should call fpga_mgr_put() when done with manager. + * + * Return: fpga manager struct or IS_ERR() condition containing error code. + */ +static struct fpga_manager *fpga_region_get_manager(struct fpga_region *region) +{ + struct device *dev = ®ion->dev; + struct device_node *np = dev->of_node; + struct device_node *mgr_node; + struct fpga_manager *mgr; + + of_node_get(np); + while (np) { + if (of_device_is_compatible(np, "fpga-region")) { + mgr_node = of_parse_phandle(np, "fpga-mgr", 0); + if (mgr_node) { + mgr = of_fpga_mgr_get(mgr_node); + of_node_put(np); + return mgr; + } + } + np = of_get_next_parent(np); + } + of_node_put(np); + + return ERR_PTR(-EINVAL); +} + +/** + * fpga_region_get_bridges - create a list of bridges + * @region: FPGA region + * @overlay: device node of the overlay + * + * Create a list of bridges including the parent bridge and the bridges + * specified by "fpga-bridges" property. Note that the + * fpga_bridges_enable/disable/put functions are all fine with an empty list + * if that happens. + * + * Caller should call fpga_bridges_put(®ion->bridge_list) when + * done with the bridges. + * + * Return 0 for success (even if there are no bridges specified) + * or -EBUSY if any of the bridges are in use. + */ +static int fpga_region_get_bridges(struct fpga_region *region, + struct device_node *overlay) +{ + struct device *dev = ®ion->dev; + struct device_node *region_np = dev->of_node; + struct device_node *br, *np, *parent_br = NULL; + int i, ret; + + /* If parent is a bridge, add to list */ + ret = fpga_bridge_get_to_list(region_np->parent, region->info, + ®ion->bridge_list); + if (ret == -EBUSY) + return ret; + + if (!ret) + parent_br = region_np->parent; + + /* If overlay has a list of bridges, use it. */ + if (of_parse_phandle(overlay, "fpga-bridges", 0)) + np = overlay; + else + np = region_np; + + for (i = 0; ; i++) { + br = of_parse_phandle(np, "fpga-bridges", i); + if (!br) + break; + + /* If parent bridge is in list, skip it. */ + if (br == parent_br) + continue; + + /* If node is a bridge, get it and add to list */ + ret = fpga_bridge_get_to_list(br, region->info, + ®ion->bridge_list); + + /* If any of the bridges are in use, give up */ + if (ret == -EBUSY) { + fpga_bridges_put(®ion->bridge_list); + return -EBUSY; + } + } + + return 0; +} + +/** + * fpga_region_program_fpga - program FPGA + * @region: FPGA region + * @firmware_name: name of FPGA image firmware file + * @overlay: device node of the overlay + * Program an FPGA using information in the device tree. + * Function assumes that there is a firmware-name property. + * Return 0 for success or negative error code. + */ +static int fpga_region_program_fpga(struct fpga_region *region, + const char *firmware_name, + struct device_node *overlay) +{ + struct fpga_manager *mgr; + int ret; + + region = fpga_region_get(region); + if (IS_ERR(region)) { + pr_err("failed to get fpga region\n"); + return PTR_ERR(region); + } + + mgr = fpga_region_get_manager(region); + if (IS_ERR(mgr)) { + pr_err("failed to get fpga region manager\n"); + return PTR_ERR(mgr); + } + + ret = fpga_region_get_bridges(region, overlay); + if (ret) { + pr_err("failed to get fpga region bridges\n"); + goto err_put_mgr; + } + + ret = fpga_bridges_disable(®ion->bridge_list); + if (ret) { + pr_err("failed to disable region bridges\n"); + goto err_put_br; + } + + ret = fpga_mgr_firmware_load(mgr, region->info, firmware_name); + if (ret) { + pr_err("failed to load fpga image\n"); + goto err_put_br; + } + + ret = fpga_bridges_enable(®ion->bridge_list); + if (ret) { + pr_err("failed to enable region bridges\n"); + goto err_put_br; + } + + fpga_mgr_put(mgr); + fpga_region_put(region); + + return 0; + +err_put_br: + fpga_bridges_put(®ion->bridge_list); +err_put_mgr: + fpga_mgr_put(mgr); + fpga_region_put(region); + + return ret; +} + +/** + * child_regions_with_firmware + * @overlay: device node of the overlay + * + * If the overlay adds child FPGA regions, they are not allowed to have + * firmware-name property. + * + * Return 0 for OK or -EINVAL if child FPGA region adds firmware-name. + */ +static int child_regions_with_firmware(struct device_node *overlay) +{ + struct device_node *child_region; + const char *child_firmware_name; + int ret = 0; + + of_node_get(overlay); + + child_region = of_find_matching_node(overlay, fpga_region_of_match); + while (child_region) { + if (!of_property_read_string(child_region, "firmware-name", + &child_firmware_name)) { + ret = -EINVAL; + break; + } + child_region = of_find_matching_node(child_region, + fpga_region_of_match); + } + + of_node_put(child_region); + + if (ret) + pr_err("firmware-name not allowed in child FPGA region: %s", + child_region->full_name); + + return ret; +} + +/** + * fpga_region_notify_pre_apply - pre-apply overlay notification + * + * @region: FPGA region that the overlay was applied to + * @nd: overlay notification data + * + * Called after when an overlay targeted to a FPGA Region is about to be + * applied. Function will check the properties that will be added to the FPGA + * region. If the checks pass, it will program the FPGA. + * + * The checks are: + * The overlay must add either firmware-name or external-fpga-config property + * to the FPGA Region. + * + * firmware-name : program the FPGA + * external-fpga-config : FPGA is already programmed + * + * The overlay can add other FPGA regions, but child FPGA regions cannot have a + * firmware-name property since those regions don't exist yet. + * + * If the overlay that breaks the rules, notifier returns an error and the + * overlay is rejected before it goes into the main tree. + * + * Returns 0 for success or negative error code for failure. + */ +static int fpga_region_notify_pre_apply(struct fpga_region *region, + struct of_overlay_notify_data *nd) +{ + const char *firmware_name = NULL; + struct fpga_image_info *info; + int ret; + + info = devm_kzalloc(®ion->dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + region->info = info; + + /* Reject overlay if child FPGA Regions have firmware-name property */ + ret = child_regions_with_firmware(nd->overlay); + if (ret) + return ret; + + /* Read FPGA region properties from the overlay */ + if (of_property_read_bool(nd->overlay, "partial-fpga-config")) + info->flags |= FPGA_MGR_PARTIAL_RECONFIG; + + if (of_property_read_bool(nd->overlay, "external-fpga-config")) + info->flags |= FPGA_MGR_EXTERNAL_CONFIG; + + of_property_read_string(nd->overlay, "firmware-name", &firmware_name); + + of_property_read_u32(nd->overlay, "region-unfreeze-timeout-us", + &info->enable_timeout_us); + + of_property_read_u32(nd->overlay, "region-freeze-timeout-us", + &info->disable_timeout_us); + + /* If FPGA was externally programmed, don't specify firmware */ + if ((info->flags & FPGA_MGR_EXTERNAL_CONFIG) && firmware_name) { + pr_err("error: specified firmware and external-fpga-config"); + return -EINVAL; + } + + /* FPGA is already configured externally. We're done. */ + if (info->flags & FPGA_MGR_EXTERNAL_CONFIG) + return 0; + + /* If we got this far, we should be programming the FPGA */ + if (!firmware_name) { + pr_err("should specify firmware-name or external-fpga-config\n"); + return -EINVAL; + } + + return fpga_region_program_fpga(region, firmware_name, nd->overlay); +} + +/** + * fpga_region_notify_post_remove - post-remove overlay notification + * + * @region: FPGA region that was targeted by the overlay that was removed + * @nd: overlay notification data + * + * Called after an overlay has been removed if the overlay's target was a + * FPGA region. + */ +static void fpga_region_notify_post_remove(struct fpga_region *region, + struct of_overlay_notify_data *nd) +{ + fpga_bridges_disable(®ion->bridge_list); + fpga_bridges_put(®ion->bridge_list); + devm_kfree(®ion->dev, region->info); + region->info = NULL; +} + +/** + * of_fpga_region_notify - reconfig notifier for dynamic DT changes + * @nb: notifier block + * @action: notifier action + * @arg: reconfig data + * + * This notifier handles programming a FPGA when a "firmware-name" property is + * added to a fpga-region. + * + * Returns NOTIFY_OK or error if FPGA programming fails. + */ +static int of_fpga_region_notify(struct notifier_block *nb, + unsigned long action, void *arg) +{ + struct of_overlay_notify_data *nd = arg; + struct fpga_region *region; + int ret; + + switch (action) { + case OF_OVERLAY_PRE_APPLY: + pr_debug("%s OF_OVERLAY_PRE_APPLY\n", __func__); + break; + case OF_OVERLAY_POST_APPLY: + pr_debug("%s OF_OVERLAY_POST_APPLY\n", __func__); + return NOTIFY_OK; /* not for us */ + case OF_OVERLAY_PRE_REMOVE: + pr_debug("%s OF_OVERLAY_PRE_REMOVE\n", __func__); + return NOTIFY_OK; /* not for us */ + case OF_OVERLAY_POST_REMOVE: + pr_debug("%s OF_OVERLAY_POST_REMOVE\n", __func__); + break; + default: /* should not happen */ + return NOTIFY_OK; + } + + region = fpga_region_find(nd->target); + if (!region) + return NOTIFY_OK; + + ret = 0; + switch (action) { + case OF_OVERLAY_PRE_APPLY: + ret = fpga_region_notify_pre_apply(region, nd); + break; + + case OF_OVERLAY_POST_REMOVE: + fpga_region_notify_post_remove(region, nd); + break; + } + + put_device(®ion->dev); + + if (ret) + return notifier_from_errno(ret); + + return NOTIFY_OK; +} + +static struct notifier_block fpga_region_of_nb = { + .notifier_call = of_fpga_region_notify, +}; + +static int fpga_region_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct fpga_region *region; + int id, ret = 0; + + region = kzalloc(sizeof(*region), GFP_KERNEL); + if (!region) + return -ENOMEM; + + id = ida_simple_get(&fpga_region_ida, 0, 0, GFP_KERNEL); + if (id < 0) { + ret = id; + goto err_kfree; + } + + mutex_init(®ion->mutex); + INIT_LIST_HEAD(®ion->bridge_list); + + device_initialize(®ion->dev); + region->dev.class = fpga_region_class; + region->dev.parent = dev; + region->dev.of_node = np; + region->dev.id = id; + dev_set_drvdata(dev, region); + + ret = dev_set_name(®ion->dev, "region%d", id); + if (ret) + goto err_remove; + + ret = device_add(®ion->dev); + if (ret) + goto err_remove; + + of_platform_populate(np, fpga_region_of_match, NULL, ®ion->dev); + + dev_info(dev, "FPGA Region probed\n"); + + return 0; + +err_remove: + ida_simple_remove(&fpga_region_ida, id); +err_kfree: + kfree(region); + + return ret; +} + +static int fpga_region_remove(struct platform_device *pdev) +{ + struct fpga_region *region = platform_get_drvdata(pdev); + + device_unregister(®ion->dev); + + return 0; +} + +static struct platform_driver fpga_region_driver = { + .probe = fpga_region_probe, + .remove = fpga_region_remove, + .driver = { + .name = "fpga-region", + .of_match_table = of_match_ptr(fpga_region_of_match), + }, +}; + +static void fpga_region_dev_release(struct device *dev) +{ + struct fpga_region *region = to_fpga_region(dev); + + ida_simple_remove(&fpga_region_ida, region->dev.id); + kfree(region); +} + +/** + * fpga_region_init - init function for fpga_region class + * Creates the fpga_region class and registers a reconfig notifier. + */ +static int __init fpga_region_init(void) +{ + int ret; + + fpga_region_class = class_create(THIS_MODULE, "fpga_region"); + if (IS_ERR(fpga_region_class)) + return PTR_ERR(fpga_region_class); + + fpga_region_class->dev_release = fpga_region_dev_release; + + ret = of_overlay_notifier_register(&fpga_region_of_nb); + if (ret) + goto err_class; + + ret = platform_driver_register(&fpga_region_driver); + if (ret) + goto err_plat; + + return 0; + +err_plat: + of_overlay_notifier_unregister(&fpga_region_of_nb); +err_class: + class_destroy(fpga_region_class); + ida_destroy(&fpga_region_ida); + return ret; +} + +static void __exit fpga_region_exit(void) +{ + platform_driver_unregister(&fpga_region_driver); + of_overlay_notifier_unregister(&fpga_region_of_nb); + class_destroy(fpga_region_class); + ida_destroy(&fpga_region_ida); +} + +subsys_initcall(fpga_region_init); +module_exit(fpga_region_exit); + +MODULE_DESCRIPTION("FPGA Region"); +MODULE_AUTHOR("Alan Tull "); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/fpga/fpga-mgr.h b/include/linux/fpga/fpga-mgr.h index 55803186e0ea..96a1a3311649 100644 --- a/include/linux/fpga/fpga-mgr.h +++ b/include/linux/fpga/fpga-mgr.h @@ -65,8 +65,10 @@ enum fpga_mgr_states { /* * FPGA Manager flags * FPGA_MGR_PARTIAL_RECONFIG: do partial reconfiguration if supported + * FPGA_MGR_EXTERNAL_CONFIG: FPGA has been configured prior to Linux booting */ #define FPGA_MGR_PARTIAL_RECONFIG BIT(0) +#define FPGA_MGR_EXTERNAL_CONFIG BIT(1) /** * struct fpga_image_info - information specific to a FPGA image -- cgit v1.2.3-71-gd317 From 7c7a6077f5c7a35fc03a7f452875d8440dd1bc8d Mon Sep 17 00:00:00 2001 From: Alexander Usyskin Date: Wed, 16 Nov 2016 22:51:29 +0200 Subject: mei: bus: split RX and async notification callbacks Split callbacks for RX and async notification events on mei bus to eliminate synchronization problems and to open way for RX optimizations. Signed-off-by: Alexander Usyskin Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/bus.c | 141 ++++++++++++++++++++++++++------------------- drivers/misc/mei/client.c | 5 ++ drivers/nfc/mei_phy.c | 36 +++++------- drivers/watchdog/mei_wdt.c | 36 ++++-------- include/linux/mei_cl_bus.h | 32 +++++----- 5 files changed, 128 insertions(+), 122 deletions(-) (limited to 'include/linux') diff --git a/drivers/misc/mei/bus.c b/drivers/misc/mei/bus.c index 483587f60249..2fd254ecde2f 100644 --- a/drivers/misc/mei/bus.c +++ b/drivers/misc/mei/bus.c @@ -209,31 +209,40 @@ ssize_t mei_cldev_recv(struct mei_cl_device *cldev, u8 *buf, size_t length) EXPORT_SYMBOL_GPL(mei_cldev_recv); /** - * mei_cl_bus_event_work - dispatch rx event for a bus device - * and schedule new work + * mei_cl_bus_rx_work - dispatch rx event for a bus device * * @work: work */ -static void mei_cl_bus_event_work(struct work_struct *work) +static void mei_cl_bus_rx_work(struct work_struct *work) { struct mei_cl_device *cldev; struct mei_device *bus; - cldev = container_of(work, struct mei_cl_device, event_work); + cldev = container_of(work, struct mei_cl_device, rx_work); bus = cldev->bus; - if (cldev->event_cb) - cldev->event_cb(cldev, cldev->events); + if (cldev->rx_cb) + cldev->rx_cb(cldev); - cldev->events = 0; + mutex_lock(&bus->device_lock); + mei_cl_read_start(cldev->cl, mei_cl_mtu(cldev->cl), NULL); + mutex_unlock(&bus->device_lock); +} - /* Prepare for the next read */ - if (cldev->events_mask & BIT(MEI_CL_EVENT_RX)) { - mutex_lock(&bus->device_lock); - mei_cl_read_start(cldev->cl, mei_cl_mtu(cldev->cl), NULL); - mutex_unlock(&bus->device_lock); - } +/** + * mei_cl_bus_notif_work - dispatch FW notif event for a bus device + * + * @work: work + */ +static void mei_cl_bus_notif_work(struct work_struct *work) +{ + struct mei_cl_device *cldev; + + cldev = container_of(work, struct mei_cl_device, notif_work); + + if (cldev->notif_cb) + cldev->notif_cb(cldev); } /** @@ -248,18 +257,13 @@ bool mei_cl_bus_notify_event(struct mei_cl *cl) { struct mei_cl_device *cldev = cl->cldev; - if (!cldev || !cldev->event_cb) - return false; - - if (!(cldev->events_mask & BIT(MEI_CL_EVENT_NOTIF))) + if (!cldev || !cldev->notif_cb) return false; if (!cl->notify_ev) return false; - set_bit(MEI_CL_EVENT_NOTIF, &cldev->events); - - schedule_work(&cldev->event_work); + schedule_work(&cldev->notif_work); cl->notify_ev = false; @@ -267,7 +271,7 @@ bool mei_cl_bus_notify_event(struct mei_cl *cl) } /** - * mei_cl_bus_rx_event - schedule rx event + * mei_cl_bus_rx_event - schedule rx event * * @cl: host client * @@ -278,64 +282,81 @@ bool mei_cl_bus_rx_event(struct mei_cl *cl) { struct mei_cl_device *cldev = cl->cldev; - if (!cldev || !cldev->event_cb) - return false; - - if (!(cldev->events_mask & BIT(MEI_CL_EVENT_RX))) + if (!cldev || !cldev->rx_cb) return false; - set_bit(MEI_CL_EVENT_RX, &cldev->events); - - schedule_work(&cldev->event_work); + schedule_work(&cldev->rx_work); return true; } /** - * mei_cldev_register_event_cb - register event callback + * mei_cldev_register_rx_cb - register Rx event callback * * @cldev: me client devices - * @event_cb: callback function - * @events_mask: requested events bitmask + * @rx_cb: callback function * * Return: 0 on success * -EALREADY if an callback is already registered * <0 on other errors */ -int mei_cldev_register_event_cb(struct mei_cl_device *cldev, - unsigned long events_mask, - mei_cldev_event_cb_t event_cb) +int mei_cldev_register_rx_cb(struct mei_cl_device *cldev, mei_cldev_cb_t rx_cb) { struct mei_device *bus = cldev->bus; int ret; - if (cldev->event_cb) + if (!rx_cb) + return -EINVAL; + if (cldev->rx_cb) return -EALREADY; - cldev->events = 0; - cldev->events_mask = events_mask; - cldev->event_cb = event_cb; - INIT_WORK(&cldev->event_work, mei_cl_bus_event_work); + cldev->rx_cb = rx_cb; + INIT_WORK(&cldev->rx_work, mei_cl_bus_rx_work); - if (cldev->events_mask & BIT(MEI_CL_EVENT_RX)) { - mutex_lock(&bus->device_lock); - ret = mei_cl_read_start(cldev->cl, mei_cl_mtu(cldev->cl), NULL); - mutex_unlock(&bus->device_lock); - if (ret && ret != -EBUSY) - return ret; - } + mutex_lock(&bus->device_lock); + ret = mei_cl_read_start(cldev->cl, mei_cl_mtu(cldev->cl), NULL); + mutex_unlock(&bus->device_lock); + if (ret && ret != -EBUSY) + return ret; - if (cldev->events_mask & BIT(MEI_CL_EVENT_NOTIF)) { - mutex_lock(&bus->device_lock); - ret = mei_cl_notify_request(cldev->cl, NULL, event_cb ? 1 : 0); - mutex_unlock(&bus->device_lock); - if (ret) - return ret; - } + return 0; +} +EXPORT_SYMBOL_GPL(mei_cldev_register_rx_cb); + +/** + * mei_cldev_register_notif_cb - register FW notification event callback + * + * @cldev: me client devices + * @notif_cb: callback function + * + * Return: 0 on success + * -EALREADY if an callback is already registered + * <0 on other errors + */ +int mei_cldev_register_notif_cb(struct mei_cl_device *cldev, + mei_cldev_cb_t notif_cb) +{ + struct mei_device *bus = cldev->bus; + int ret; + + if (!notif_cb) + return -EINVAL; + + if (cldev->notif_cb) + return -EALREADY; + + cldev->notif_cb = notif_cb; + INIT_WORK(&cldev->notif_work, mei_cl_bus_notif_work); + + mutex_lock(&bus->device_lock); + ret = mei_cl_notify_request(cldev->cl, NULL, 1); + mutex_unlock(&bus->device_lock); + if (ret) + return ret; return 0; } -EXPORT_SYMBOL_GPL(mei_cldev_register_event_cb); +EXPORT_SYMBOL_GPL(mei_cldev_register_notif_cb); /** * mei_cldev_get_drvdata - driver data getter @@ -471,8 +492,6 @@ int mei_cldev_disable(struct mei_cl_device *cldev) bus = cldev->bus; - cldev->event_cb = NULL; - mutex_lock(&bus->device_lock); if (!mei_cl_is_connected(cl)) { @@ -619,9 +638,13 @@ static int mei_cl_device_remove(struct device *dev) if (!cldev || !dev->driver) return 0; - if (cldev->event_cb) { - cldev->event_cb = NULL; - cancel_work_sync(&cldev->event_work); + if (cldev->rx_cb) { + cancel_work_sync(&cldev->rx_work); + cldev->rx_cb = NULL; + } + if (cldev->notif_cb) { + cancel_work_sync(&cldev->notif_work); + cldev->notif_cb = NULL; } cldrv = to_mei_cl_driver(dev->driver); diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c index 46ee9155ada6..9635b14b6011 100644 --- a/drivers/misc/mei/client.c +++ b/drivers/misc/mei/client.c @@ -673,6 +673,11 @@ int mei_cl_unlink(struct mei_cl *cl) list_del_init(&cl->link); cl->state = MEI_FILE_UNINITIALIZED; + cl->writing_state = MEI_IDLE; + + WARN_ON(!list_empty(&cl->rd_completed) || + !list_empty(&cl->rd_pending) || + !list_empty(&cl->link)); return 0; } diff --git a/drivers/nfc/mei_phy.c b/drivers/nfc/mei_phy.c index 03139c5a05e4..8a04c5e02999 100644 --- a/drivers/nfc/mei_phy.c +++ b/drivers/nfc/mei_phy.c @@ -297,9 +297,11 @@ static int mei_nfc_recv(struct nfc_mei_phy *phy, u8 *buf, size_t length) } -static void nfc_mei_event_cb(struct mei_cl_device *cldev, u32 events) +static void nfc_mei_rx_cb(struct mei_cl_device *cldev) { struct nfc_mei_phy *phy = mei_cldev_get_drvdata(cldev); + struct sk_buff *skb; + int reply_size; if (!phy) return; @@ -307,27 +309,22 @@ static void nfc_mei_event_cb(struct mei_cl_device *cldev, u32 events) if (phy->hard_fault != 0) return; - if (events & BIT(MEI_CL_EVENT_RX)) { - struct sk_buff *skb; - int reply_size; - - skb = alloc_skb(MEI_NFC_MAX_READ, GFP_KERNEL); - if (!skb) - return; + skb = alloc_skb(MEI_NFC_MAX_READ, GFP_KERNEL); + if (!skb) + return; - reply_size = mei_nfc_recv(phy, skb->data, MEI_NFC_MAX_READ); - if (reply_size < MEI_NFC_HEADER_SIZE) { - kfree_skb(skb); - return; - } + reply_size = mei_nfc_recv(phy, skb->data, MEI_NFC_MAX_READ); + if (reply_size < MEI_NFC_HEADER_SIZE) { + kfree_skb(skb); + return; + } - skb_put(skb, reply_size); - skb_pull(skb, MEI_NFC_HEADER_SIZE); + skb_put(skb, reply_size); + skb_pull(skb, MEI_NFC_HEADER_SIZE); - MEI_DUMP_SKB_IN("mei frame read", skb); + MEI_DUMP_SKB_IN("mei frame read", skb); - nfc_hci_recv_frame(phy->hdev, skb); - } + nfc_hci_recv_frame(phy->hdev, skb); } static int nfc_mei_phy_enable(void *phy_id) @@ -358,8 +355,7 @@ static int nfc_mei_phy_enable(void *phy_id) goto err; } - r = mei_cldev_register_event_cb(phy->cldev, BIT(MEI_CL_EVENT_RX), - nfc_mei_event_cb); + r = mei_cldev_register_rx_cb(phy->cldev, nfc_mei_rx_cb); if (r) { pr_err("Event cb registration failed %d\n", r); goto err; diff --git a/drivers/watchdog/mei_wdt.c b/drivers/watchdog/mei_wdt.c index e0af52265511..79b35515904e 100644 --- a/drivers/watchdog/mei_wdt.c +++ b/drivers/watchdog/mei_wdt.c @@ -410,11 +410,11 @@ static void mei_wdt_unregister_work(struct work_struct *work) } /** - * mei_wdt_event_rx - callback for data receive + * mei_wdt_rx - callback for data receive * * @cldev: bus device */ -static void mei_wdt_event_rx(struct mei_cl_device *cldev) +static void mei_wdt_rx(struct mei_cl_device *cldev) { struct mei_wdt *wdt = mei_cldev_get_drvdata(cldev); struct mei_wdt_start_response res; @@ -482,11 +482,11 @@ out: } /* - * mei_wdt_notify_event - callback for event notification + * mei_wdt_notif - callback for event notification * * @cldev: bus device */ -static void mei_wdt_notify_event(struct mei_cl_device *cldev) +static void mei_wdt_notif(struct mei_cl_device *cldev) { struct mei_wdt *wdt = mei_cldev_get_drvdata(cldev); @@ -496,21 +496,6 @@ static void mei_wdt_notify_event(struct mei_cl_device *cldev) mei_wdt_register(wdt); } -/** - * mei_wdt_event - callback for event receive - * - * @cldev: bus device - * @events: event mask - */ -static void mei_wdt_event(struct mei_cl_device *cldev, u32 events) -{ - if (events & BIT(MEI_CL_EVENT_RX)) - mei_wdt_event_rx(cldev); - - if (events & BIT(MEI_CL_EVENT_NOTIF)) - mei_wdt_notify_event(cldev); -} - #if IS_ENABLED(CONFIG_DEBUG_FS) static ssize_t mei_dbgfs_read_activation(struct file *file, char __user *ubuf, @@ -621,16 +606,17 @@ static int mei_wdt_probe(struct mei_cl_device *cldev, goto err_out; } - ret = mei_cldev_register_event_cb(wdt->cldev, - BIT(MEI_CL_EVENT_RX) | - BIT(MEI_CL_EVENT_NOTIF), - mei_wdt_event); + ret = mei_cldev_register_rx_cb(wdt->cldev, mei_wdt_rx); + if (ret) { + dev_err(&cldev->dev, "Could not reg rx event ret=%d\n", ret); + goto err_disable; + } + ret = mei_cldev_register_notif_cb(wdt->cldev, mei_wdt_notif); /* on legacy devices notification is not supported - * this doesn't fail the registration for RX event */ if (ret && ret != -EOPNOTSUPP) { - dev_err(&cldev->dev, "Could not register event ret=%d\n", ret); + dev_err(&cldev->dev, "Could not reg notif event ret=%d\n", ret); goto err_disable; } diff --git a/include/linux/mei_cl_bus.h b/include/linux/mei_cl_bus.h index 4adb2e7c9f84..017f5232b3de 100644 --- a/include/linux/mei_cl_bus.h +++ b/include/linux/mei_cl_bus.h @@ -8,8 +8,7 @@ struct mei_cl_device; struct mei_device; -typedef void (*mei_cldev_event_cb_t)(struct mei_cl_device *cldev, - u32 events); +typedef void (*mei_cldev_cb_t)(struct mei_cl_device *cldev); /** * struct mei_cl_device - MEI device handle @@ -24,11 +23,12 @@ typedef void (*mei_cldev_event_cb_t)(struct mei_cl_device *cldev, * @me_cl: me client * @cl: mei client * @name: device name - * @event_work: async work to execute event callback - * @event_cb: Drivers register this callback to get asynchronous ME - * events (e.g. Rx buffer pending) notifications. - * @events_mask: Events bit mask requested by driver. - * @events: Events bitmask sent to the driver. + * @rx_work: async work to execute Rx event callback + * @rx_cb: Drivers register this callback to get asynchronous ME + * Rx buffer pending notifications. + * @notif_work: async work to execute FW notif event callback + * @notif_cb: Drivers register this callback to get asynchronous ME + * FW notification pending notifications. * * @do_match: wheather device can be matched with a driver * @is_added: device is already scanned @@ -43,10 +43,10 @@ struct mei_cl_device { struct mei_cl *cl; char name[MEI_CL_NAME_SIZE]; - struct work_struct event_work; - mei_cldev_event_cb_t event_cb; - unsigned long events_mask; - unsigned long events; + struct work_struct rx_work; + mei_cldev_cb_t rx_cb; + struct work_struct notif_work; + mei_cldev_cb_t notif_cb; unsigned int do_match:1; unsigned int is_added:1; @@ -88,13 +88,9 @@ void mei_cldev_driver_unregister(struct mei_cl_driver *cldrv); ssize_t mei_cldev_send(struct mei_cl_device *cldev, u8 *buf, size_t length); ssize_t mei_cldev_recv(struct mei_cl_device *cldev, u8 *buf, size_t length); -int mei_cldev_register_event_cb(struct mei_cl_device *cldev, - unsigned long event_mask, - mei_cldev_event_cb_t read_cb); - -#define MEI_CL_EVENT_RX 0 -#define MEI_CL_EVENT_TX 1 -#define MEI_CL_EVENT_NOTIF 2 +int mei_cldev_register_rx_cb(struct mei_cl_device *cldev, mei_cldev_cb_t rx_cb); +int mei_cldev_register_notif_cb(struct mei_cl_device *cldev, + mei_cldev_cb_t notif_cb); const uuid_le *mei_cldev_uuid(const struct mei_cl_device *cldev); u8 mei_cldev_ver(const struct mei_cl_device *cldev); -- cgit v1.2.3-71-gd317 From 1d7f1589d341344c0c598b00de44891a7968c6a0 Mon Sep 17 00:00:00 2001 From: Jason Gunthorpe Date: Tue, 22 Nov 2016 18:22:09 +0000 Subject: fpga: Clarify how write_init works streaming modes This interface was designed for streaming, but write_init's buf argument has an unclear purpose. Define it to be the first bytes of the bitstream. Each driver gets to set how many bytes (at most) it wants to see. Short bitstreams will be passed through as-is, while long ones will be truncated. The intent is to allow drivers to peek at the header before the transfer actually starts. Signed-off-by: Jason Gunthorpe Acked-by: Alan Tull --- Documentation/fpga/fpga-mgr.txt | 5 ++++- drivers/fpga/fpga-mgr.c | 6 ++++-- drivers/fpga/socfpga-a10.c | 1 + include/linux/fpga/fpga-mgr.h | 2 ++ 4 files changed, 11 insertions(+), 3 deletions(-) (limited to 'include/linux') diff --git a/Documentation/fpga/fpga-mgr.txt b/Documentation/fpga/fpga-mgr.txt index 087924f2b20c..86ee5078fd03 100644 --- a/Documentation/fpga/fpga-mgr.txt +++ b/Documentation/fpga/fpga-mgr.txt @@ -169,7 +169,10 @@ The programming sequence is: 2. .write (may be called once or multiple times) 3. .write_complete -The .write_init function will prepare the FPGA to receive the image data. +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, +if the whole bitstream is not immediately available then the core code will +buffer up at least this much before starting. The .write function writes a buffer to the FPGA. The buffer may be contain the whole FPGA image or may be a smaller chunk of an FPGA image. In the latter diff --git a/drivers/fpga/fpga-mgr.c b/drivers/fpga/fpga-mgr.c index 79ce2eea44db..f0a69d3e60a5 100644 --- a/drivers/fpga/fpga-mgr.c +++ b/drivers/fpga/fpga-mgr.c @@ -53,10 +53,12 @@ int fpga_mgr_buf_load(struct fpga_manager *mgr, struct fpga_image_info *info, /* * Call the low level driver's write_init function. This will do the * device-specific things to get the FPGA into the state where it is - * ready to receive an FPGA image. + * ready to receive an FPGA image. The low level driver only gets to + * see the first initial_header_size bytes in the buffer. */ mgr->state = FPGA_MGR_STATE_WRITE_INIT; - ret = mgr->mops->write_init(mgr, info, buf, count); + ret = mgr->mops->write_init(mgr, info, buf, + min(mgr->mops->initial_header_size, count)); if (ret) { dev_err(dev, "Error preparing FPGA for writing\n"); mgr->state = FPGA_MGR_STATE_WRITE_INIT_ERR; diff --git a/drivers/fpga/socfpga-a10.c b/drivers/fpga/socfpga-a10.c index ccd9fb23bd52..f8770af0f6b5 100644 --- a/drivers/fpga/socfpga-a10.c +++ b/drivers/fpga/socfpga-a10.c @@ -470,6 +470,7 @@ static enum fpga_mgr_states socfpga_a10_fpga_state(struct fpga_manager *mgr) } static const struct fpga_manager_ops socfpga_a10_fpga_mgr_ops = { + .initial_header_size = (RBF_DECOMPRESS_OFFSET + 1) * 4, .state = socfpga_a10_fpga_state, .write_init = socfpga_a10_fpga_write_init, .write = socfpga_a10_fpga_write, diff --git a/include/linux/fpga/fpga-mgr.h b/include/linux/fpga/fpga-mgr.h index 96a1a3311649..16551d5eac36 100644 --- a/include/linux/fpga/fpga-mgr.h +++ b/include/linux/fpga/fpga-mgr.h @@ -84,6 +84,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 * @write_init: prepare the FPGA to receive confuration data * @write: write count bytes of configuration data to the FPGA @@ -95,6 +96,7 @@ struct fpga_image_info { * called, so leaving them out is fine. */ struct fpga_manager_ops { + size_t initial_header_size; enum fpga_mgr_states (*state)(struct fpga_manager *mgr); int (*write_init)(struct fpga_manager *mgr, struct fpga_image_info *info, -- cgit v1.2.3-71-gd317 From 8d7cc7adcec55bfeb28cc2dc58b996c95f180337 Mon Sep 17 00:00:00 2001 From: Martyn Welch Date: Sat, 3 Dec 2016 23:45:01 +0000 Subject: VME: Remove shutdown entry from vme_driver The vme_driver structure currently has a "shutdown" entry. This entry is never used, it lacks the correct parameter (it should be providing a pointer to the relevant vme_dev struct to even *look* usable), the VME subsystem currently doesn't provide support for shutdown functions and no in-tree drivers use it (hardly surprising, given it'd never be called). Remove the entry from vme_driver to avoid confusion. Signed-off-by: Martyn Welch Signed-off-by: Greg Kroah-Hartman --- include/linux/vme.h | 1 - 1 file changed, 1 deletion(-) (limited to 'include/linux') diff --git a/include/linux/vme.h b/include/linux/vme.h index ea6095deba20..8c589176c2f8 100644 --- a/include/linux/vme.h +++ b/include/linux/vme.h @@ -113,7 +113,6 @@ struct vme_driver { int (*match)(struct vme_dev *); int (*probe)(struct vme_dev *); int (*remove)(struct vme_dev *); - void (*shutdown)(void); struct device_driver driver; struct list_head devices; }; -- cgit v1.2.3-71-gd317 From 076802d0061520bc646ee121fe0f34fce6c55a93 Mon Sep 17 00:00:00 2001 From: Alexander Usyskin Date: Sat, 3 Dec 2016 00:15:25 +0200 Subject: mei: bus: enable non-blocking RX Enable non-blocking receive for drivers on mei bus, this allows checking for data availability by mei client drivers. This is most effective for fixed address clients, that lacks flow control. This function adds new API function mei_cldev_recv_nonblock(), it retuns -EGAIN if function will block. Signed-off-by: Alexander Usyskin Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/bus-fixup.c | 4 ++-- drivers/misc/mei/bus.c | 31 +++++++++++++++++++++++++++++-- drivers/misc/mei/mei_dev.h | 7 ++++++- include/linux/mei_cl_bus.h | 6 ++++-- 4 files changed, 41 insertions(+), 7 deletions(-) (limited to 'include/linux') diff --git a/drivers/misc/mei/bus-fixup.c b/drivers/misc/mei/bus-fixup.c index 7f2cef9011ae..18e05ca7584f 100644 --- a/drivers/misc/mei/bus-fixup.c +++ b/drivers/misc/mei/bus-fixup.c @@ -141,7 +141,7 @@ static int mei_osver(struct mei_cl_device *cldev) if (ret < 0) return ret; - ret = __mei_cl_recv(cldev->cl, buf, length); + ret = __mei_cl_recv(cldev->cl, buf, length, 0); if (ret < 0) return ret; @@ -272,7 +272,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); + bytes_recv = __mei_cl_recv(cl, (u8 *)reply, if_version_length, 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 2fd254ecde2f..0037153c80a6 100644 --- a/drivers/misc/mei/bus.c +++ b/drivers/misc/mei/bus.c @@ -98,15 +98,18 @@ out: * @cl: host client * @buf: buffer to receive * @length: buffer length + * @mode: io mode * * Return: read size in bytes of < 0 on error */ -ssize_t __mei_cl_recv(struct mei_cl *cl, u8 *buf, size_t length) +ssize_t __mei_cl_recv(struct mei_cl *cl, u8 *buf, size_t length, + unsigned int mode) { struct mei_device *bus; struct mei_cl_cb *cb; size_t r_length; ssize_t rets; + bool nonblock = !!(mode & MEI_CL_IO_RX_NONBLOCK); if (WARN_ON(!cl || !cl->dev)) return -ENODEV; @@ -127,6 +130,11 @@ ssize_t __mei_cl_recv(struct mei_cl *cl, u8 *buf, size_t length) if (rets && rets != -EBUSY) goto out; + if (nonblock) { + rets = -EAGAIN; + goto out; + } + /* wait on event only if there is no other waiter */ /* synchronized under device mutex */ if (!waitqueue_active(&cl->rx_wait)) { @@ -191,6 +199,25 @@ ssize_t mei_cldev_send(struct mei_cl_device *cldev, u8 *buf, size_t length) } EXPORT_SYMBOL_GPL(mei_cldev_send); +/** + * mei_cldev_recv_nonblock - non block client receive (read) + * + * @cldev: me client device + * @buf: buffer to receive + * @length: buffer length + * + * Return: read size in bytes of < 0 on error + * -EAGAIN if function will block. + */ +ssize_t mei_cldev_recv_nonblock(struct mei_cl_device *cldev, u8 *buf, + size_t length) +{ + struct mei_cl *cl = cldev->cl; + + return __mei_cl_recv(cl, buf, length, MEI_CL_IO_RX_NONBLOCK); +} +EXPORT_SYMBOL_GPL(mei_cldev_recv_nonblock); + /** * mei_cldev_recv - client receive (read) * @@ -204,7 +231,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); + return __mei_cl_recv(cl, buf, length, 0); } EXPORT_SYMBOL_GPL(mei_cldev_recv); diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index 0e94df517410..699693cd8c59 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -115,10 +115,14 @@ enum mei_cb_file_ops { * * @MEI_CL_IO_TX_BLOCKING: send is blocking * @MEI_CL_IO_TX_INTERNAL: internal communication between driver and FW + * + * @MEI_CL_IO_RX_NONBLOCK: recv is non-blocking */ enum mei_cl_io_mode { MEI_CL_IO_TX_BLOCKING = BIT(0), MEI_CL_IO_TX_INTERNAL = BIT(1), + + MEI_CL_IO_RX_NONBLOCK = BIT(2), }; /* @@ -319,7 +323,8 @@ void mei_cl_bus_rescan_work(struct work_struct *work); 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); +ssize_t __mei_cl_recv(struct mei_cl *cl, u8 *buf, size_t length, + unsigned int mode); 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); diff --git a/include/linux/mei_cl_bus.h b/include/linux/mei_cl_bus.h index 017f5232b3de..a0d274fe08f1 100644 --- a/include/linux/mei_cl_bus.h +++ b/include/linux/mei_cl_bus.h @@ -75,7 +75,7 @@ void mei_cldev_driver_unregister(struct mei_cl_driver *cldrv); /** * module_mei_cl_driver - Helper macro for registering mei cl driver * - * @__mei_cldrv mei_cl_driver structure + * @__mei_cldrv: mei_cl_driver structure * * Helper macro for mei cl drivers which do not do anything special in module * init/exit, for eliminating a boilerplate code. @@ -86,7 +86,9 @@ void mei_cldev_driver_unregister(struct mei_cl_driver *cldrv); mei_cldev_driver_unregister) ssize_t mei_cldev_send(struct mei_cl_device *cldev, u8 *buf, size_t length); -ssize_t mei_cldev_recv(struct mei_cl_device *cldev, u8 *buf, size_t length); +ssize_t mei_cldev_recv(struct mei_cl_device *cldev, u8 *buf, size_t length); +ssize_t mei_cldev_recv_nonblock(struct mei_cl_device *cldev, u8 *buf, + size_t length); int mei_cldev_register_rx_cb(struct mei_cl_device *cldev, mei_cldev_cb_t rx_cb); int mei_cldev_register_notif_cb(struct mei_cl_device *cldev, -- cgit v1.2.3-71-gd317 From f45be72c8ec0b85263d1fe1e6c681d8c87e198e6 Mon Sep 17 00:00:00 2001 From: Haiyang Zhang Date: Sat, 3 Dec 2016 12:34:29 -0800 Subject: hyperv: Fix spelling of HV_UNKOWN Changed it to HV_UNKNOWN Signed-off-by: Haiyang Zhang Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman --- drivers/hv/channel_mgmt.c | 6 +++--- include/linux/hyperv.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'include/linux') diff --git a/drivers/hv/channel_mgmt.c b/drivers/hv/channel_mgmt.c index cbb96f2f0d1a..e5d1b0e6c922 100644 --- a/drivers/hv/channel_mgmt.c +++ b/drivers/hv/channel_mgmt.c @@ -134,7 +134,7 @@ static const struct vmbus_device vmbus_devs[] = { }, /* Unknown GUID */ - { .dev_type = HV_UNKOWN, + { .dev_type = HV_UNKNOWN, .perf_device = false, }, }; @@ -163,9 +163,9 @@ static u16 hv_get_dev_type(const struct vmbus_channel *channel) u16 i; if (is_hvsock_channel(channel) || is_unsupported_vmbus_devs(guid)) - return HV_UNKOWN; + return HV_UNKNOWN; - for (i = HV_IDE; i < HV_UNKOWN; i++) { + for (i = HV_IDE; i < HV_UNKNOWN; i++) { if (!uuid_le_cmp(*guid, vmbus_devs[i].guid)) return i; } diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index 2a52d9abb305..35053f99522b 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -696,7 +696,7 @@ enum vmbus_device_type { HV_FCOPY, HV_BACKUP, HV_DM, - HV_UNKOWN, + HV_UNKNOWN, }; struct vmbus_device { -- cgit v1.2.3-71-gd317 From fc76936d3ea5720a6e0948a08381b803a68deb28 Mon Sep 17 00:00:00 2001 From: Stephen Hemminger Date: Sat, 3 Dec 2016 12:34:39 -0800 Subject: vmbus: add support for dynamic device id's This patch adds sysfs interface to dynamically bind new UUID values to existing VMBus device. This is useful for generic UIO driver to act similar to uio_pci_generic. Signed-off-by: Stephen Hemminger Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman --- drivers/hv/vmbus_drv.c | 174 ++++++++++++++++++++++++++++++++++++++++++++++--- include/linux/hyperv.h | 6 ++ 2 files changed, 172 insertions(+), 8 deletions(-) (limited to 'include/linux') diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c index 0276d2ef06ee..230c62e7f567 100644 --- a/drivers/hv/vmbus_drv.c +++ b/drivers/hv/vmbus_drv.c @@ -45,6 +45,11 @@ #include #include "hyperv_vmbus.h" +struct vmbus_dynid { + struct list_head node; + struct hv_vmbus_device_id id; +}; + static struct acpi_device *hv_acpi_dev; static struct completion probe_event; @@ -500,7 +505,7 @@ static ssize_t device_show(struct device *dev, static DEVICE_ATTR_RO(device); /* Set up per device attributes in /sys/bus/vmbus/devices/ */ -static struct attribute *vmbus_attrs[] = { +static struct attribute *vmbus_dev_attrs[] = { &dev_attr_id.attr, &dev_attr_state.attr, &dev_attr_monitor_id.attr, @@ -528,7 +533,7 @@ static struct attribute *vmbus_attrs[] = { &dev_attr_device.attr, NULL, }; -ATTRIBUTE_GROUPS(vmbus); +ATTRIBUTE_GROUPS(vmbus_dev); /* * vmbus_uevent - add uevent for our device @@ -565,10 +570,29 @@ static inline bool is_null_guid(const uuid_le *guid) * Return a matching hv_vmbus_device_id pointer. * If there is no match, return NULL. */ -static const struct hv_vmbus_device_id *hv_vmbus_get_id( - const struct hv_vmbus_device_id *id, +static const struct hv_vmbus_device_id *hv_vmbus_get_id(struct hv_driver *drv, const uuid_le *guid) { + const struct hv_vmbus_device_id *id = NULL; + struct vmbus_dynid *dynid; + + /* Look at the dynamic ids first, before the static ones */ + spin_lock(&drv->dynids.lock); + list_for_each_entry(dynid, &drv->dynids.list, node) { + if (!uuid_le_cmp(dynid->id.guid, *guid)) { + id = &dynid->id; + break; + } + } + spin_unlock(&drv->dynids.lock); + + if (id) + return id; + + id = drv->id_table; + if (id == NULL) + return NULL; /* empty device table */ + for (; !is_null_guid(&id->guid); id++) if (!uuid_le_cmp(id->guid, *guid)) return id; @@ -576,6 +600,134 @@ static const struct hv_vmbus_device_id *hv_vmbus_get_id( return NULL; } +/* vmbus_add_dynid - add a new device ID to this driver and re-probe devices */ +static int vmbus_add_dynid(struct hv_driver *drv, uuid_le *guid) +{ + struct vmbus_dynid *dynid; + + dynid = kzalloc(sizeof(*dynid), GFP_KERNEL); + if (!dynid) + return -ENOMEM; + + dynid->id.guid = *guid; + + spin_lock(&drv->dynids.lock); + list_add_tail(&dynid->node, &drv->dynids.list); + spin_unlock(&drv->dynids.lock); + + return driver_attach(&drv->driver); +} + +static void vmbus_free_dynids(struct hv_driver *drv) +{ + struct vmbus_dynid *dynid, *n; + + spin_lock(&drv->dynids.lock); + list_for_each_entry_safe(dynid, n, &drv->dynids.list, node) { + list_del(&dynid->node); + kfree(dynid); + } + spin_unlock(&drv->dynids.lock); +} + +/* Parse string of form: 1b4e28ba-2fa1-11d2-883f-b9a761bde3f */ +static int get_uuid_le(const char *str, uuid_le *uu) +{ + unsigned int b[16]; + int i; + + if (strlen(str) < 37) + return -1; + + for (i = 0; i < 36; i++) { + switch (i) { + case 8: case 13: case 18: case 23: + if (str[i] != '-') + return -1; + break; + default: + if (!isxdigit(str[i])) + return -1; + } + } + + /* unparse little endian output byte order */ + if (sscanf(str, + "%2x%2x%2x%2x-%2x%2x-%2x%2x-%2x%2x-%2x%2x%2x%2x%2x%2x", + &b[3], &b[2], &b[1], &b[0], + &b[5], &b[4], &b[7], &b[6], &b[8], &b[9], + &b[10], &b[11], &b[12], &b[13], &b[14], &b[15]) != 16) + return -1; + + for (i = 0; i < 16; i++) + uu->b[i] = b[i]; + return 0; +} + +/* + * store_new_id - sysfs frontend to vmbus_add_dynid() + * + * Allow GUIDs to be added to an existing driver via sysfs. + */ +static ssize_t new_id_store(struct device_driver *driver, const char *buf, + size_t count) +{ + struct hv_driver *drv = drv_to_hv_drv(driver); + uuid_le guid = NULL_UUID_LE; + ssize_t retval; + + if (get_uuid_le(buf, &guid) != 0) + return -EINVAL; + + if (hv_vmbus_get_id(drv, &guid)) + return -EEXIST; + + retval = vmbus_add_dynid(drv, &guid); + if (retval) + return retval; + return count; +} +static DRIVER_ATTR_WO(new_id); + +/* + * store_remove_id - remove a PCI device ID from this driver + * + * Removes a dynamic pci device ID to this driver. + */ +static ssize_t remove_id_store(struct device_driver *driver, const char *buf, + size_t count) +{ + struct hv_driver *drv = drv_to_hv_drv(driver); + struct vmbus_dynid *dynid, *n; + uuid_le guid = NULL_UUID_LE; + size_t retval = -ENODEV; + + if (get_uuid_le(buf, &guid)) + return -EINVAL; + + spin_lock(&drv->dynids.lock); + list_for_each_entry_safe(dynid, n, &drv->dynids.list, node) { + struct hv_vmbus_device_id *id = &dynid->id; + + if (!uuid_le_cmp(id->guid, guid)) { + list_del(&dynid->node); + kfree(dynid); + retval = count; + break; + } + } + spin_unlock(&drv->dynids.lock); + + return retval; +} +static DRIVER_ATTR_WO(remove_id); + +static struct attribute *vmbus_drv_attrs[] = { + &driver_attr_new_id.attr, + &driver_attr_remove_id.attr, + NULL, +}; +ATTRIBUTE_GROUPS(vmbus_drv); /* @@ -590,7 +742,7 @@ static int vmbus_match(struct device *device, struct device_driver *driver) if (is_hvsock_channel(hv_dev->channel)) return drv->hvsock; - if (hv_vmbus_get_id(drv->id_table, &hv_dev->dev_type)) + if (hv_vmbus_get_id(drv, &hv_dev->dev_type)) return 1; return 0; @@ -607,7 +759,7 @@ static int vmbus_probe(struct device *child_device) struct hv_device *dev = device_to_hv_device(child_device); const struct hv_vmbus_device_id *dev_id; - dev_id = hv_vmbus_get_id(drv->id_table, &dev->dev_type); + dev_id = hv_vmbus_get_id(drv, &dev->dev_type); if (drv->probe) { ret = drv->probe(dev, dev_id); if (ret != 0) @@ -684,7 +836,8 @@ static struct bus_type hv_bus = { .remove = vmbus_remove, .probe = vmbus_probe, .uevent = vmbus_uevent, - .dev_groups = vmbus_groups, + .dev_groups = vmbus_dev_groups, + .drv_groups = vmbus_drv_groups, }; struct onmessage_work_context { @@ -905,6 +1058,9 @@ int __vmbus_driver_register(struct hv_driver *hv_driver, struct module *owner, c hv_driver->driver.mod_name = mod_name; hv_driver->driver.bus = &hv_bus; + spin_lock_init(&hv_driver->dynids.lock); + INIT_LIST_HEAD(&hv_driver->dynids.list); + ret = driver_register(&hv_driver->driver); return ret; @@ -923,8 +1079,10 @@ void vmbus_driver_unregister(struct hv_driver *hv_driver) { pr_info("unregistering driver %s\n", hv_driver->name); - if (!vmbus_exists()) + if (!vmbus_exists()) { driver_unregister(&hv_driver->driver); + vmbus_free_dynids(hv_driver); + } } EXPORT_SYMBOL_GPL(vmbus_driver_unregister); diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index 35053f99522b..42fe43fb0c80 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -1119,6 +1119,12 @@ struct hv_driver { struct device_driver driver; + /* dynamic device GUID's */ + struct { + spinlock_t lock; + struct list_head list; + } dynids; + int (*probe)(struct hv_device *, const struct hv_vmbus_device_id *); int (*remove)(struct hv_device *); void (*shutdown)(struct hv_device *); -- cgit v1.2.3-71-gd317