[PATCH] rtnet: Fix lifecycle management of mapped rtskbs
Jan Kiszka
jan.kiszka at web.de
Fri Jan 18 13:37:36 CET 2019
On 18.01.19 12:58, Philippe Gerum via Xenomai wrote:
> On 1/17/19 1:56 PM, Philippe Gerum wrote:
>> On 12/18/18 7:46 AM, Jan Kiszka via Xenomai wrote:
>>> On 14.12.18 12:22, Jan Kiszka via Xenomai wrote:
>>>> Do not add rtskbs to the rtskb_list which are not mappend because
>>>> rtdev_unmap_rtskb will not remove such rtskbs again (buf_dma_addr ==
>>>> RTSKB_UNMAPPED). In fact, rtskb_list should be called rtskb_mapped_list,
>>>> so refactor this while at it.
>>>>
>>>> Signed-off-by: Jan Kiszka <jan.kiszka at siemens.com>
>>>> ---
>>>> kernel/drivers/net/stack/rtdev.c | 10 +++++-----
>>>> 1 file changed, 5 insertions(+), 5 deletions(-)
>>>>
>>>> diff --git a/kernel/drivers/net/stack/rtdev.c
>>>> b/kernel/drivers/net/stack/rtdev.c
>>>> index 5eb73ce1a4..7b267f2a7c 100644
>>>> --- a/kernel/drivers/net/stack/rtdev.c
>>>> +++ b/kernel/drivers/net/stack/rtdev.c
>>>> @@ -44,9 +44,9 @@ MODULE_PARM_DESC(device_rtskbs, "Number of
>>>> additional global realtime socket "
>>>> struct rtnet_device *rtnet_devices[MAX_RT_DEVICES];
>>>> static struct rtnet_device *loopback_device;
>>>> static DEFINE_RTDM_LOCK(rtnet_devices_rt_lock);
>>>> +static LIST_HEAD(rtskb_mapped_list);
>>>> LIST_HEAD(event_hook_list);
>>>> -LIST_HEAD(rtskb_list);
>>>> DEFINE_MUTEX(rtnet_devices_nrt_lock);
>>>> static int rtdev_locked_xmit(struct rtskb *skb, struct
>>>> rtnet_device *rtdev);
>>>> @@ -412,8 +412,8 @@ int rtdev_map_rtskb(struct rtskb *skb)
>>>> }
>>>> }
>>>> - if (!err)
>>>> - list_add(&skb->entry, &rtskb_list);
>>>> + if (!err && skb->buf_dma_addr != RTSKB_UNMAPPED)
>>>> + list_add(&skb->entry, &rtskb_mapped_list);
>>>> mutex_unlock(&rtnet_devices_nrt_lock);
>>>> @@ -430,7 +430,7 @@ static int rtdev_map_all_rtskbs(struct
>>>> rtnet_device *rtdev)
>>>> if (!rtdev->map_rtskb)
>>>> return 0;
>>>> - list_for_each_entry(skb, &rtskb_list, entry) {
>>>> + list_for_each_entry(skb, &rtskb_mapped_list, entry) {
>>>> err = rtskb_map(rtdev, skb);
>>>> if (err)
>>>> break;
>>>> @@ -474,7 +474,7 @@ static void rtdev_unmap_all_rtskbs(struct
>>>> rtnet_device *rtdev)
>>>> if (!rtdev->unmap_rtskb)
>>>> return;
>>>> - list_for_each_entry(skb, &rtskb_list, entry) {
>>>> + list_for_each_entry(skb, &rtskb_mapped_list, entry) {
>>>> rtdev->unmap_rtskb(rtdev, skb);
>>>> }
>>>> }
>>>>
>>>
>>> Applied to master and stable.
>>>
>>> Jan
>>>
>>
>> Unfortunately, this breaks RTnet for any driver using DMA, apparently
>> uncovering a pre-existing issue in the device init->registration logic
>> wrt mapping skb from per-device pools.
>>
>> In short, filtering skbs we can't immediately map out of the rtskb list
>> in rtdev_map_rtskb() prevents rt_register_rtnetdev(dev) from finding
>> @dev's skbs in rtskb_mapped_list when running rtdev_map_all_skbs().
>> In turn this prevents the driver from setting up the hw's RX/TX rings
>> with proper DMA mapping addresses, as none of its skbs has valid bus
>> addresses for DMA.
>>
>> There may be a simple fix, but the rtdev init logic is a bit too
>> convoluted for my after lunch. I'll try to come up with a fix later.
>>
>
> So there is a catch 22 situation with skb mapping for DMA operations. It
> stems from the fact that creating a netdevice via a call to
> rt_alloc_etherdev(dev) can't successfully map the skbs attached to that
> new device, because rtdev_map_rtskb() only operates on registered
> netdevices, which the new one cannot be just yet as it is only half-baked.
>
> Prior to that change, the code worked almost by accident because
> registering that new device would end up calling rtdev_map_all_rtskbs(),
> which would eventually find the unmapped skbs left in the global
> rtskb_list and map them through the driver's ->map_rtskb handler.
>
> There are only two in-tree drivers using the ->map_rtskb handler, namely
> igb and e1000e, I'm working on adding a 3rd one with a refactoring of
> the fec for i.MX SoCs, which is why I hit this bug.
>
> I suspect ->map_rtskb was introduced to address yet another issue with
> all other DMA-capable drivers: calling dma_{map, unmap}_single() from
> I/O handlers in primary mode like they all do is unsafe, and this
> handler may have been intended to ensure those operations happen from
> regular Linux context instead. As a matter of fact, most existing RTnet
> drivers are unsafe regarding this. Generally speaking, it looks like the
> process of initializing a new netdevice has evolved over time in RTnet,
> leading to a lot of open coding on the driver developer's end, which is
> quite error-prone.
>
> My take on this is that the init->registration logic may need a rework
> and a better API, solving the issue of skb mapping in the same move,
> fixing or simply dropping obsolete driver code too. However, doing so
> without breaking more stuff would require significant RTnet background I
> don't have, so I'm leaving this to the grown-ups.
>
> In the meantime, this is a workaround addressing the skb mapping bug
> introduced by this commit (only lightly tested so far):
>
> commit 31ae7b2154bc0d8df841835d699087876bec3014
> Author: Philippe Gerum <rpm at xenomai.org>
> Date: Thu Jan 17 18:17:43 2019 +0100
>
> net/rtdev: ensure per-device skbs get mapped at registration
>
> This patch works around a regression introduced by #74464ee37d0,
> causing a new device's skbs not to be passed to its ->map_rtskb()
> handler when registered, breaking further DMA inits in the driver.
>
> diff --git a/kernel/drivers/net/stack/rtdev.c
> b/kernel/drivers/net/stack/rtdev.c
> index 2982740d0..c5f8c73c6 100644
> --- a/kernel/drivers/net/stack/rtdev.c
> +++ b/kernel/drivers/net/stack/rtdev.c
> @@ -45,6 +45,7 @@ struct rtnet_device
> *rtnet_devices[MAX_RT_DEVICES];
> static struct rtnet_device *loopback_device;
> static DEFINE_RTDM_LOCK(rtnet_devices_rt_lock);
> static LIST_HEAD(rtskb_mapped_list);
> +static LIST_HEAD(rtskb_mapwait_list);
> LIST_HEAD(event_hook_list);
> DEFINE_MUTEX(rtnet_devices_nrt_lock);
> @@ -459,8 +460,12 @@ int rtdev_map_rtskb(struct rtskb *skb)
> }
> }
> - if (!err && skb->buf_dma_addr != RTSKB_UNMAPPED)
> - list_add(&skb->entry, &rtskb_mapped_list);
> + if (!err) {
> + if (skb->buf_dma_addr != RTSKB_UNMAPPED)
> + list_add(&skb->entry, &rtskb_mapped_list);
> + else
> + list_add(&skb->entry, &rtskb_mapwait_list);
> + }
> mutex_unlock(&rtnet_devices_nrt_lock);
> @@ -471,19 +476,31 @@ int rtdev_map_rtskb(struct rtskb *skb)
> static int rtdev_map_all_rtskbs(struct rtnet_device *rtdev)
> {
> - struct rtskb *skb;
> - int err = 0;
> + struct rtskb *skb, *n;
> + int err;
> if (!rtdev->map_rtskb)
> - return 0;
> + return 0;
> - list_for_each_entry(skb, &rtskb_mapped_list, entry) {
> - err = rtskb_map(rtdev, skb);
> - if (err)
> - break;
> + if (!list_empty(&rtskb_mapped_list)) {
> + list_for_each_entry(skb, &rtskb_mapped_list, entry) {
> + err = rtskb_map(rtdev, skb);
> + if (err)
> + return err;
> + }
> }
> - return err;
> + if (!list_empty(&rtskb_mapwait_list)) {
> + list_for_each_entry_safe(skb, n, &rtskb_mapwait_list, entry) {
> + err = rtskb_map(rtdev, skb);
> + if (err)
> + return err;
> + list_del(&skb->entry);
> + list_add(&skb->entry, &rtskb_mapped_list);
> + }
> + }
> +
> + return 0;
> }
>
>
OK, thanks for digging into that. Yes, the RTnet code carries a lot of history,
and parts that I completely forgot by now (it's 15 years old, or more).
If that workaround also pleases our smokey test via lookback dev, I can merge.
Jan
More information about the Xenomai
mailing list