Re: [PATCH 1/1] Bluetooth: Prioritize SCO traffic on slow interfaces

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi Abhishek,

>>>>> When scheduling TX packets, send all SCO/eSCO packets first and then
>>>>> send only 1 ACL/LE packet in a loop while checking that there are no SCO
>>>>> packets pending. This is done to make sure that we can meet SCO
>>>>> deadlines on slow interfaces like UART. If we were to queue up multiple
>>>>> ACL packets without checking for a SCO packet, we might miss the SCO
>>>>> timing. For example:
>>>>> 
>>>>> The time it takes to send a maximum size ACL packet (1024 bytes):
>>>>> t = 10/8 * 1024 bytes * 8 bits/byte * 1 packet / baudrate
>>>>>      where 10/8 is uart overhead due to start/stop bits per byte
>>>>> 
>>>>> Replace t = 3.75ms (SCO deadline), which gives us a baudrate of 2730666
>>>>> and is pretty close to a common baudrate of 3000000 used for BT. At this
>>>>> baudrate, if we sent two 1024 byte ACL packets, we would miss the 3.75ms
>>>>> timing window.
>>>>> 
>>>>> Signed-off-by: Abhishek Pandit-Subedi <abhishekpandit@xxxxxxxxxxxx>
>>>>> ---
>>>>> 
>>>>> include/net/bluetooth/hci_core.h |  1 +
>>>>> net/bluetooth/hci_core.c         | 91 +++++++++++++++++++++++++-------
>>>>> 2 files changed, 73 insertions(+), 19 deletions(-)
>>>>> 
>>>>> diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h
>>>>> index d4e28773d378..f636c89f1fe1 100644
>>>>> --- a/include/net/bluetooth/hci_core.h
>>>>> +++ b/include/net/bluetooth/hci_core.h
>>>>> @@ -315,6 +315,7 @@ struct hci_dev {
>>>>>     __u8            ssp_debug_mode;
>>>>>     __u8            hw_error_code;
>>>>>     __u32           clock;
>>>>> +     __u8            sched_limit;
>>>> 
>>>> why do you need this parameter?
>>> 
>>> This is really only necessary on systems where the data transfer rate
>>> to the controller is low. I want the driver to set whether we should
>>> aggressively schedule SCO packets. A quirk might actually be better
>>> than a variable (wasn't sure what is preferable).
>> 
>> or maybe we try without driver choice first. I would assume what is required for UART, will not harm USB or SDIO transports either.
> 
> Ack -- I can make this default behavior.
> 
>> 
>>>>>     __u16           devid_source;
>>>>>     __u16           devid_vendor;
>>>>> diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c
>>>>> index dbd2ad3a26ed..00a72265cd96 100644
>>>>> --- a/net/bluetooth/hci_core.c
>>>>> +++ b/net/bluetooth/hci_core.c
>>>>> @@ -4239,18 +4239,32 @@ static void __check_timeout(struct hci_dev *hdev, unsigned int cnt)
>>>>>     }
>>>>> }
>>>>> 
>>>>> -static void hci_sched_acl_pkt(struct hci_dev *hdev)
>>>>> +/* Limit packets in flight when SCO/eSCO links are active. */
>>>>> +static bool hci_sched_limit(struct hci_dev *hdev)
>>>>> +{
>>>>> +     return hdev->sched_limit && hci_conn_num(hdev, SCO_LINK);
>>>>> +}
>>>>> +
>>>>> +static bool hci_sched_acl_pkt(struct hci_dev *hdev)
>>>>> {
>>>>>     unsigned int cnt = hdev->acl_cnt;
>>>>>     struct hci_chan *chan;
>>>>>     struct sk_buff *skb;
>>>>>     int quote;
>>>>> +     bool sched_limit = hci_sched_limit(hdev);
>>>>> +     bool resched = false;
>>>>> 
>>>>>     __check_timeout(hdev, cnt);
>>>>> 
>>>>>     while (hdev->acl_cnt &&
>>>>>            (chan = hci_chan_sent(hdev, ACL_LINK, &quote))) {
>>>>>             u32 priority = (skb_peek(&chan->data_q))->priority;
>>>>> +
>>>>> +             if (sched_limit && quote > 0) {
>>>>> +                     resched = true;
>>>>> +                     quote = 1;
>>>>> +             }
>>>>> +
>>>>>             while (quote-- && (skb = skb_peek(&chan->data_q))) {
>>>>>                     BT_DBG("chan %p skb %p len %d priority %u", chan, skb,
>>>>>                            skb->len, skb->priority);
>>>>> @@ -4271,19 +4285,26 @@ static void hci_sched_acl_pkt(struct hci_dev *hdev)
>>>>>                     chan->sent++;
>>>>>                     chan->conn->sent++;
>>>>>             }
>>>>> +
>>>>> +             if (resched && cnt != hdev->acl_cnt)
>>>>> +                     break;
>>>>>     }
>>>>> 
>>>>> -     if (cnt != hdev->acl_cnt)
>>>>> +     if (hdev->acl_cnt == 0 && cnt != hdev->acl_cnt)
>>>>>             hci_prio_recalculate(hdev, ACL_LINK);
>>>>> +
>>>>> +     return resched;
>>>>> }
>>>>> 
>>>>> -static void hci_sched_acl_blk(struct hci_dev *hdev)
>>>>> +static bool hci_sched_acl_blk(struct hci_dev *hdev)
>>>>> {
>>>>>     unsigned int cnt = hdev->block_cnt;
>>>>>     struct hci_chan *chan;
>>>>>     struct sk_buff *skb;
>>>>>     int quote;
>>>>>     u8 type;
>>>>> +     bool sched_limit = hci_sched_limit(hdev);
>>>>> +     bool resched = false;
>>>>> 
>>>>>     __check_timeout(hdev, cnt);
>>>>> 
>>>>> @@ -4297,6 +4318,12 @@ static void hci_sched_acl_blk(struct hci_dev *hdev)
>>>>>     while (hdev->block_cnt > 0 &&
>>>>>            (chan = hci_chan_sent(hdev, type, &quote))) {
>>>>>             u32 priority = (skb_peek(&chan->data_q))->priority;
>>>>> +
>>>>> +             if (sched_limit && quote > 0) {
>>>>> +                     resched = true;
>>>>> +                     quote = 1;
>>>>> +             }
>>>>> +
>>>>>             while (quote > 0 && (skb = skb_peek(&chan->data_q))) {
>>>>>                     int blocks;
>>>>> 
>>>>> @@ -4311,7 +4338,7 @@ static void hci_sched_acl_blk(struct hci_dev *hdev)
>>>>> 
>>>>>                     blocks = __get_blocks(hdev, skb);
>>>>>                     if (blocks > hdev->block_cnt)
>>>>> -                             return;
>>>>> +                             return false;
>>>>> 
>>>>>                     hci_conn_enter_active_mode(chan->conn,
>>>>>                                                bt_cb(skb)->force_active);
>>>>> @@ -4325,33 +4352,39 @@ static void hci_sched_acl_blk(struct hci_dev *hdev)
>>>>>                     chan->sent += blocks;
>>>>>                     chan->conn->sent += blocks;
>>>>>             }
>>>>> +
>>>>> +             if (resched && cnt != hdev->block_cnt)
>>>>> +                     break;
>>>>>     }
>>>>> 
>>>>> -     if (cnt != hdev->block_cnt)
>>>>> +     if (hdev->block_cnt == 0 && cnt != hdev->block_cnt)
>>>>>             hci_prio_recalculate(hdev, type);
>>>>> +
>>>>> +     return resched;
>>>>> }
>>>>> 
>>>>> -static void hci_sched_acl(struct hci_dev *hdev)
>>>>> +static bool hci_sched_acl(struct hci_dev *hdev)
>>>>> {
>>>>>     BT_DBG("%s", hdev->name);
>>>>> 
>>>>>     /* No ACL link over BR/EDR controller */
>>>>>     if (!hci_conn_num(hdev, ACL_LINK) && hdev->dev_type == HCI_PRIMARY)
>>>>> -             return;
>>>>> +             goto done;
>>>> 
>>>> Style wise the goto done is overkill. Just return false.
>>> 
>>> Will do.
>>> 
>>>> 
>>>>> 
>>>>>     /* No AMP link over AMP controller */
>>>>>     if (!hci_conn_num(hdev, AMP_LINK) && hdev->dev_type == HCI_AMP)
>>>>> -             return;
>>>>> +             goto done;
>>>>> 
>>>>>     switch (hdev->flow_ctl_mode) {
>>>>>     case HCI_FLOW_CTL_MODE_PACKET_BASED:
>>>>> -             hci_sched_acl_pkt(hdev);
>>>>> -             break;
>>>>> +             return hci_sched_acl_pkt(hdev);
>>>>> 
>>>>>     case HCI_FLOW_CTL_MODE_BLOCK_BASED:
>>>>> -             hci_sched_acl_blk(hdev);
>>>>> -             break;
>>>>> +             return hci_sched_acl_blk(hdev);
>>>> 
>>>> So the block based mode is for AMP controllers and not used on BR/EDR controllers. Since AMP controllers only transport ACL packet and no SCO/eSCO packets, we can ignore this here.
>>> 
>>> Ok, I'll remove it there.
>>> 
>>>> 
>>>>>     }
>>>>> +
>>>>> +done:
>>>>> +     return false;
>>>>> }
>>>>> 
>>>>> /* Schedule SCO */
>>>>> @@ -4402,16 +4435,18 @@ static void hci_sched_esco(struct hci_dev *hdev)
>>>>>     }
>>>>> }
>>>>> 
>>>>> -static void hci_sched_le(struct hci_dev *hdev)
>>>>> +static bool hci_sched_le(struct hci_dev *hdev)
>>>>> {
>>>>>     struct hci_chan *chan;
>>>>>     struct sk_buff *skb;
>>>>>     int quote, cnt, tmp;
>>>>> +     bool sched_limit = hci_sched_limit(hdev);
>>>>> +     bool resched = false;
>>>>> 
>>>>>     BT_DBG("%s", hdev->name);
>>>>> 
>>>>>     if (!hci_conn_num(hdev, LE_LINK))
>>>>> -             return;
>>>>> +             return resched;
>>>>> 
>>>>>     cnt = hdev->le_pkts ? hdev->le_cnt : hdev->acl_cnt;
>>>>> 
>>>>> @@ -4420,6 +4455,12 @@ static void hci_sched_le(struct hci_dev *hdev)
>>>>>     tmp = cnt;
>>>>>     while (cnt && (chan = hci_chan_sent(hdev, LE_LINK, &quote))) {
>>>>>             u32 priority = (skb_peek(&chan->data_q))->priority;
>>>>> +
>>>>> +             if (sched_limit && quote > 0) {
>>>>> +                     resched = true;
>>>>> +                     quote = 1;
>>>>> +             }
>>>>> +
>>>>>             while (quote-- && (skb = skb_peek(&chan->data_q))) {
>>>>>                     BT_DBG("chan %p skb %p len %d priority %u", chan, skb,
>>>>>                            skb->len, skb->priority);
>>>>> @@ -4437,6 +4478,9 @@ static void hci_sched_le(struct hci_dev *hdev)
>>>>>                     chan->sent++;
>>>>>                     chan->conn->sent++;
>>>>>             }
>>>>> +
>>>>> +             if (resched && cnt != tmp)
>>>>> +                     break;
>>>>>     }
>>>>> 
>>>>>     if (hdev->le_pkts)
>>>>> @@ -4444,24 +4488,33 @@ static void hci_sched_le(struct hci_dev *hdev)
>>>>>     else
>>>>>             hdev->acl_cnt = cnt;
>>>>> 
>>>>> -     if (cnt != tmp)
>>>>> +     if (cnt == 0 && cnt != tmp)
>>>>>             hci_prio_recalculate(hdev, LE_LINK);
>>>>> +
>>>>> +     return resched;
>>>>> }
>>>>> 
>>>>> static void hci_tx_work(struct work_struct *work)
>>>>> {
>>>>>     struct hci_dev *hdev = container_of(work, struct hci_dev, tx_work);
>>>>>     struct sk_buff *skb;
>>>>> +     bool resched;
>>>>> 
>>>>>     BT_DBG("%s acl %d sco %d le %d", hdev->name, hdev->acl_cnt,
>>>>>            hdev->sco_cnt, hdev->le_cnt);
>>>>> 
>>>>>     if (!hci_dev_test_flag(hdev, HCI_USER_CHANNEL)) {
>>>>>             /* Schedule queues and send stuff to HCI driver */
>>>>> -             hci_sched_acl(hdev);
>>>>> -             hci_sched_sco(hdev);
>>>>> -             hci_sched_esco(hdev);
>>>>> -             hci_sched_le(hdev);
>>>>> +             do {
>>>>> +                     /* SCO and eSCO send all packets until emptied */
>>>>> +                     hci_sched_sco(hdev);
>>>>> +                     hci_sched_esco(hdev);
>>>>> +
>>>>> +                     /* Acl and Le send based on quota (priority on ACL per
>>>>> +                      * loop)
>>>>> +                      */
>>>>> +                     resched = hci_sched_acl(hdev) || hci_sched_le(hdev);
>>>>> +             } while (resched);
>>>>>     }
>>>> 
>>>> I am not in favor of this busy loop. We might want to re-think the whole scheduling by connection type and really only focus on scheduling ACL (BR/EDR and LE) and audio packets (SCO/eSCO and ISO).
>>> 
>>> I think the busy loop is the simplest solution if we want to solve the
>>> problem: don't send 2 ACL packets without checking if there is a SCO
>>> packet scheduled (which is the worst case I'm worried about on UART
>>> interfaces).
>>> 
>>> If we get rid of the connection type scheduling and only do audio and
>>> ACL, we would still need some mechanism to guarantee that you don't
>>> send >~1100 bytes without checking if SCO is queued (assuming 3000000
>>> baudrate and 3.75ms latency requirement).
>> 
>> Why don’t we just say that if SCO is queued up, then after each ACL packet we should send a SCO packet.
> 
> That sounds good. Effectively, this is what I wanted to achieve
> without modifying the ACL round robin mechanism too much.

Lets try this first and see how it goes. We might need to do some modifications, but it is worth a try.

>>>> In addition, we also need to check that SCO scheduling and A2DP media channel ACL packets do work together. I think that generally it would be best to have a clear rate at which SCO packets are require to pushed down to the hardware. So you really reserve bandwidth and not blindly prioritize them via a busy loop.
>>>> 
>>> I am less worried about bandwidth and more about latency. If I start
>>> sending really large ACL packets through UART, it could take multiple
>>> milliseconds. It really has to be reserved bandwidth per small
>>> timeslice (like 3.75ms) so I can guarantee that if a SCO packet is
>>> seen within that time slice, it will be transferred. There will still
>>> have to be a busy loop though because the amount of data you can send
>>> in the time slice will probably be less than the data that can be
>>> in-flight to the controller (i.e. acl_max_pkts).
>> 
>> Right now we kinda let the SCO socket application provide the correct timing. I was thinking that the kernel might need to enforce this.
> 
> I was under the assumption that the Num Completed Pkts event would
> actually help us regulate the timing (assuming controller sends that
> event once it actually sends SCO packet over the air). Currently, we
> don't seem to be using it for SCO.

That is a good question and I don’t have an answer on the top of my head. We would have to check the spec text and see if controllers really follow it.

> For the next patch revision, I will remove the driver specific enable,
> gotos and scheduling of acl block. I'll also add a limit to SCO
> packets sent so it observes and respects the number of sco packets
> completed (same as ACL).
> 
> I'm not yet comfortable refactoring the scheduling from per connection
> to per type, especially as I'm not sure what to do with ISO or ACL
> audio. I think those will require a bit more thought.

>From a packet perspective, we have ACL and SCO/eSCO packets and ISO will be another type. That is just how it is defined  in the spec. However we are putting everything into data_q and I wonder if we should just add priority handling to the SCO/eSCO scheduling. We have done this for ACL so that high priority L2CAP connections come first. We would just need a way to say SCO/eSCO comes first.

Regards

Marcel




[Index of Archives]     [Bluez Devel]     [Linux Wireless Networking]     [Linux Wireless Personal Area Networking]     [Linux ATH6KL]     [Linux USB Devel]     [Linux Media Drivers]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Big List of Linux Books]

  Powered by Linux