Re: [PATCH V2 3/3] dma: add Qualcomm Technologies HIDMA channel driver

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

 






On 11/3/2015 5:10 AM, Andy Shevchenko wrote:
On Mon, Nov 2, 2015 at 8:07 AM, Sinan Kaya <okaya@xxxxxxxxxxxxxx> wrote:
This patch adds support for hidma engine. The driver
consists of two logical blocks. The DMA engine interface
and the low-level interface. The hardware only supports
memcpy/memset and this driver only support memcpy
interface. HW and driver doesn't support slave interface.

+/* Linux Foundation elects GPLv2 license only.
+ */

One line?
ok


+#include <linux/dmaengine.h>
+#include <linux/dma-mapping.h>
+#include <asm/dma.h>

Do you need this one explicitly?

got rid of it


+#include <linux/atomic.h>
+#include <linux/pm_runtime.h>

+ empty line?
ok

+#include <asm/div64.h>

+ empty line?
ok

+#include "dmaengine.h"
+#include "qcom_hidma.h"
+
+/* Default idle time is 2 seconds. This parameter can
+ * be overridden by changing the following
+ * /sys/bus/platform/devices/QCOM8061:<xy>/power/autosuspend_delay_ms
+ * during kernel boot.
+ */

ok

Block comments usually like
/*
  * text
  */


+struct hidma_chan {
+       bool                            paused;
+       bool                            allocated;
+       char                            name[16];

So, do you need specific name? There is already one in struct dma_chan.
OK, removed.

+/* process completed descriptors */
+static void hidma_process_completed(struct hidma_dev *mdma)
+{
+       dma_cookie_t last_cookie = 0;
+       struct hidma_chan *mchan;
+       struct hidma_desc *mdesc;
+       struct dma_async_tx_descriptor *desc;
+       unsigned long irqflags;
+       LIST_HEAD(list);
+       struct dma_chan *dmach = NULL;
+
+       list_for_each_entry(dmach, &mdma->ddev.channels,
+                       device_node) {
+               mchan = to_hidma_chan(dmach);
+
Found a bug here now. I should have initialized the list on each iteration of the loop.

+               /* Get all completed descriptors */
+               spin_lock_irqsave(&mchan->lock, irqflags);
+               if (!list_empty(&mchan->completed))

Removed this one.

+                       list_splice_tail_init(&mchan->completed, &list);
+               spin_unlock_irqrestore(&mchan->lock, irqflags);
+
+               if (list_empty(&list))
+                       continue;


Redundant check. It's done in both list_for_each_entry() and
list_splice_tail_init().

ok

+
+               /* Execute callbacks and run dependencies */
+               list_for_each_entry(mdesc, &list, node) {
+                       desc = &mdesc->desc;
+
+                       spin_lock_irqsave(&mchan->lock, irqflags);
+                       dma_cookie_complete(desc);
+                       spin_unlock_irqrestore(&mchan->lock, irqflags);
+
+                       if (desc->callback &&
+                               (hidma_ll_status(mdma->lldev, mdesc->tre_ch)
+                               == DMA_COMPLETE))
+                               desc->callback(desc->callback_param);
+
+                       last_cookie = desc->cookie;
+                       dma_run_dependencies(desc);
+               }
+
+               /* Free descriptors */
+               spin_lock_irqsave(&mchan->lock, irqflags);
+               list_splice_tail_init(&list, &mchan->free);
+               spin_unlock_irqrestore(&mchan->lock, irqflags);
+       }
+}
+
+/*
+ * Execute all queued DMA descriptors.
+ * This function is called either on the first transfer attempt in tx_submit
+ * or from the callback routine when one transfer is finished. It can only be
+ * called from a single location since both of places check active list to be
+ * empty and will immediately fill the active list while lock is held.
+ *
+ * Following requirements must be met while calling hidma_execute():
+ *     a) mchan->lock is locked,
+ *     b) mchan->active list contains multiple entries.
+ *     c) pm protected
+ */
+static int hidma_execute(struct hidma_chan *mchan)
+{
+       struct hidma_dev *mdma = mchan->dmadev;
+       int rc;
+
+       if (!hidma_ll_isenabled(mdma->lldev))
+               return -ENODEV;
+
+       /* Start the transfer */
+       if (!list_empty(&mchan->active))
+               rc = hidma_ll_start(mdma->lldev);
+
+       return 0;
+}
+
+/*
+ * Called once for each submitted descriptor.
+ * PM is locked once for each descriptor that is currently
+ * in execution.
+ */
+static void hidma_callback(void *data)
+{
+       struct hidma_desc *mdesc = data;
+       struct hidma_chan *mchan = to_hidma_chan(mdesc->desc.chan);
+       unsigned long irqflags;
+       struct dma_device *ddev = mchan->chan.device;
+       struct hidma_dev *dmadev = to_hidma_dev(ddev);
+       bool queued = false;
+
+       dev_dbg(dmadev->ddev.dev, "callback: data:0x%p\n", data);
+
+       spin_lock_irqsave(&mchan->lock, irqflags);
+
+       if (mdesc->node.next) {
+               /* Delete from the active list, add to completed list */
+               list_move_tail(&mdesc->node, &mchan->completed);
+               queued = true;
+       }
+       spin_unlock_irqrestore(&mchan->lock, irqflags);
+
+       hidma_process_completed(dmadev);
+
+       if (queued) {
+               pm_runtime_mark_last_busy(dmadev->ddev.dev);
+               pm_runtime_put_autosuspend(dmadev->ddev.dev);
+       }
+}
+
+static int hidma_chan_init(struct hidma_dev *dmadev, u32 dma_sig)
+{
+       struct hidma_chan *mchan;
+       struct dma_device *ddev;
+
+       mchan = devm_kzalloc(dmadev->ddev.dev, sizeof(*mchan), GFP_KERNEL);
+       if (!mchan)
+               return -ENOMEM;
+
+       ddev = &dmadev->ddev;
+       mchan->dma_sig = dma_sig;
+       mchan->dmadev = dmadev;
+       mchan->chan.device = ddev;
+       dma_cookie_init(&mchan->chan);
+
+       INIT_LIST_HEAD(&mchan->free);
+       INIT_LIST_HEAD(&mchan->prepared);
+       INIT_LIST_HEAD(&mchan->active);
+       INIT_LIST_HEAD(&mchan->completed);
+
+       spin_lock_init(&mchan->lock);
+       list_add_tail(&mchan->chan.device_node, &ddev->channels);
+       dmadev->ddev.chancnt++;
+       return 0;
+}
+
+static void hidma_issue_pending(struct dma_chan *dmach)
+{

Wrong. It should actually start the transfer. tx_submit() just puts
the descriptor to a queue.

Depends on the design.

I started from the Freescale driver (mpc512x_dma.c). It follows the same model.

I'll just drop the same comment into this code too.


/*
* We are posting descriptors to the hardware as soon as
* they are ready, so this function does nothing.
*/

+}
+
+static enum dma_status hidma_tx_status(struct dma_chan *dmach,
+                                       dma_cookie_t cookie,
+                                       struct dma_tx_state *txstate)
+{
+       enum dma_status ret;
+       unsigned long irqflags;
+       struct hidma_chan *mchan = to_hidma_chan(dmach);
+
+       spin_lock_irqsave(&mchan->lock, irqflags);

So, what are you protecting here? paused member, right?

yes.


+       if (mchan->paused)
+               ret = DMA_PAUSED;
+       else
+               ret = dma_cookie_status(dmach, cookie, txstate);

This one has no need to be under spin lock.
ok, will remove it. Apparently, other drivers are not using locks either in this routine.

+       spin_unlock_irqrestore(&mchan->lock, irqflags);
+
+       return ret;
+}
+
+/*
+ * Submit descriptor to hardware.
+ * Lock the PM for each descriptor we are sending.
+ */
+static dma_cookie_t hidma_tx_submit(struct dma_async_tx_descriptor *txd)
+{
+       struct hidma_chan *mchan = to_hidma_chan(txd->chan);
+       struct hidma_dev *dmadev = mchan->dmadev;
+       struct hidma_desc *mdesc;
+       unsigned long irqflags;
+       dma_cookie_t cookie;
+
+       if (!hidma_ll_isenabled(dmadev->lldev))
+               return -ENODEV;
+
+       pm_runtime_get_sync(dmadev->ddev.dev);

No point to do it here. It should be done on the function that
actually starts the transfer (see issue pending).

comment above

+       mdesc = container_of(txd, struct hidma_desc, desc);
+       spin_lock_irqsave(&mchan->lock, irqflags);
+
+       /* Move descriptor to active */
+       list_move_tail(&mdesc->node, &mchan->active);
+
+       /* Update cookie */
+       cookie = dma_cookie_assign(txd);
+
+       hidma_ll_queue_request(dmadev->lldev, mdesc->tre_ch);
+       hidma_execute(mchan);
+
+       spin_unlock_irqrestore(&mchan->lock, irqflags);
+
+       return cookie;
+}
+
+static int hidma_alloc_chan_resources(struct dma_chan *dmach)
+{
+       struct hidma_chan *mchan = to_hidma_chan(dmach);
+       struct hidma_dev *dmadev = mchan->dmadev;
+       int rc = 0;
+       struct hidma_desc *mdesc, *tmp;
+       unsigned long irqflags;
+       LIST_HEAD(descs);
+       u32 i;
+
+       if (mchan->allocated)
+               return 0;
+
+       /* Alloc descriptors for this channel */
+       for (i = 0; i < dmadev->nr_descriptors; i++) {
+               mdesc = kzalloc(sizeof(struct hidma_desc), GFP_KERNEL);
+               if (!mdesc) {
+                       dev_err(dmadev->ddev.dev, "Memory allocation error. ");
+                       rc = -ENOMEM;
+                       break;
+               }
+               dma_async_tx_descriptor_init(&mdesc->desc, dmach);
+               mdesc->desc.flags = DMA_CTRL_ACK;
+               mdesc->desc.tx_submit = hidma_tx_submit;
+
+               rc = hidma_ll_request(dmadev->lldev,
+                               mchan->dma_sig, "DMA engine", hidma_callback,
+                               mdesc, &mdesc->tre_ch);
+               if (rc != 1) {

if (rc < 1) {

I'll fix hidma_ll_request instead and return 0 on success and change this line as if (rc)


+                       dev_err(dmach->device->dev,
+                               "channel alloc failed at %u\n", i);

+                       kfree(mdesc);
+                       break;
+               }
+               list_add_tail(&mdesc->node, &descs);
+       }
+
+       if (rc != 1) {

if (rc < 1)

Fixed this too

+               /* return the allocated descriptors */
+               list_for_each_entry_safe(mdesc, tmp, &descs, node) {
+                       hidma_ll_free(dmadev->lldev, mdesc->tre_ch);
+                       kfree(mdesc);
+               }
+               return rc;
+       }
+
+       spin_lock_irqsave(&mchan->lock, irqflags);
+       list_splice_tail_init(&descs, &mchan->free);
+       mchan->allocated = true;
+       spin_unlock_irqrestore(&mchan->lock, irqflags);
+       dev_dbg(dmadev->ddev.dev,
+               "allocated channel for %u\n", mchan->dma_sig);
+       return rc;
+}
+
+static void hidma_free_chan_resources(struct dma_chan *dmach)
+{
+       struct hidma_chan *mchan = to_hidma_chan(dmach);
+       struct hidma_dev *mdma = mchan->dmadev;
+       struct hidma_desc *mdesc, *tmp;
+       unsigned long irqflags;
+       LIST_HEAD(descs);
+
+       if (!list_empty(&mchan->prepared) ||
+               !list_empty(&mchan->active) ||
+               !list_empty(&mchan->completed)) {
+               /* We have unfinished requests waiting.
+                * Terminate the request from the hardware.
+                */
+               hidma_cleanup_pending_tre(mdma->lldev, 0x77, 0x77);

0x77 is magic.

Changing with meaningful macros.


+
+               /* Give enough time for completions to be called. */
+               msleep(100);
+       }
+
+       spin_lock_irqsave(&mchan->lock, irqflags);
+       /* Channel must be idle */
+       WARN_ON(!list_empty(&mchan->prepared));
+       WARN_ON(!list_empty(&mchan->active));
+       WARN_ON(!list_empty(&mchan->completed));
+
+       /* Move data */
+       list_splice_tail_init(&mchan->free, &descs);
+
+       /* Free descriptors */
+       list_for_each_entry_safe(mdesc, tmp, &descs, node) {
+               hidma_ll_free(mdma->lldev, mdesc->tre_ch);
+               list_del(&mdesc->node);
+               kfree(mdesc);
+       }
+
+       mchan->allocated = 0;
+       spin_unlock_irqrestore(&mchan->lock, irqflags);
+       dev_dbg(mdma->ddev.dev, "freed channel for %u\n", mchan->dma_sig);
+}
+
+
+static struct dma_async_tx_descriptor *
+hidma_prep_dma_memcpy(struct dma_chan *dmach, dma_addr_t dma_dest,
+                       dma_addr_t dma_src, size_t len, unsigned long flags)
+{
+       struct hidma_chan *mchan = to_hidma_chan(dmach);
+       struct hidma_desc *mdesc = NULL;
+       struct hidma_dev *mdma = mchan->dmadev;
+       unsigned long irqflags;
+
+       dev_dbg(mdma->ddev.dev,
+               "memcpy: chan:%p dest:%pad src:%pad len:%zu\n", mchan,
+               &dma_dest, &dma_src, len);
+
+       /* Get free descriptor */
+       spin_lock_irqsave(&mchan->lock, irqflags);
+       if (!list_empty(&mchan->free)) {
+               mdesc = list_first_entry(&mchan->free, struct hidma_desc,
+                                       node);
+               list_del(&mdesc->node);
+       }
+       spin_unlock_irqrestore(&mchan->lock, irqflags);
+
+       if (!mdesc)
+               return NULL;
+
+       hidma_ll_set_transfer_params(mdma->lldev, mdesc->tre_ch,
+                       dma_src, dma_dest, len, flags);
+
+       /* Place descriptor in prepared list */
+       spin_lock_irqsave(&mchan->lock, irqflags);
+       list_add_tail(&mdesc->node, &mchan->prepared);
+       spin_unlock_irqrestore(&mchan->lock, irqflags);
+
+       return &mdesc->desc;
+}
+
+static int hidma_terminate_all(struct dma_chan *chan)
+{
+       struct hidma_dev *dmadev;
+       LIST_HEAD(head);
+       unsigned long irqflags;
+       LIST_HEAD(list);
+       struct hidma_desc *tmp, *mdesc = NULL;
+       int rc = 0;

Useless assignment.

removed.


+       struct hidma_chan *mchan;
+
+       mchan = to_hidma_chan(chan);
+       dmadev = to_hidma_dev(mchan->chan.device);
+       dev_dbg(dmadev->ddev.dev, "terminateall: chan:0x%p\n", mchan);
+
+       pm_runtime_get_sync(dmadev->ddev.dev);
+       /* give completed requests a chance to finish */
+       hidma_process_completed(dmadev);
+
+       spin_lock_irqsave(&mchan->lock, irqflags);
+       list_splice_init(&mchan->active, &list);
+       list_splice_init(&mchan->prepared, &list);
+       list_splice_init(&mchan->completed, &list);
+       spin_unlock_irqrestore(&mchan->lock, irqflags);
+
+       /* this suspends the existing transfer */
+       rc = hidma_ll_pause(dmadev->lldev);
+       if (rc) {
+               dev_err(dmadev->ddev.dev, "channel did not pause\n");
+               goto out;
+       }
+
+       /* return all user requests */
+       list_for_each_entry_safe(mdesc, tmp, &list, node) {
+               struct dma_async_tx_descriptor  *txd = &mdesc->desc;
+               dma_async_tx_callback callback = mdesc->desc.callback;
+               void *param = mdesc->desc.callback_param;
+               enum dma_status status;
+
+               dma_descriptor_unmap(txd);
+
+               status = hidma_ll_status(dmadev->lldev, mdesc->tre_ch);
+               /*
+                * The API requires that no submissions are done from a
+                * callback, so we don't need to drop the lock here
+                */
+               if (callback && (status == DMA_COMPLETE))
+                       callback(param);
+
+               dma_run_dependencies(txd);
+
+               /* move myself to free_list */
+               list_move(&mdesc->node, &mchan->free);
+       }
+
+       /* reinitialize the hardware */
+       rc = hidma_ll_setup(dmadev->lldev);
+
+out:
+       pm_runtime_mark_last_busy(dmadev->ddev.dev);
+       pm_runtime_put_autosuspend(dmadev->ddev.dev);
+       return rc;
+}
+
+static int hidma_pause(struct dma_chan *chan)
+{
+       struct hidma_chan *mchan;
+       struct hidma_dev *dmadev;
+
+       mchan = to_hidma_chan(chan);
+       dmadev = to_hidma_dev(mchan->chan.device);
+       dev_dbg(dmadev->ddev.dev, "pause: chan:0x%p\n", mchan);
+
+       pm_runtime_get_sync(dmadev->ddev.dev);

Why it's here? Here is nothing to do with the device, move it to _pause().


I'll move it inside the if statement. hidma_ll_pause touches the hardware.

+       if (!mchan->paused) {
+               if (hidma_ll_pause(dmadev->lldev))
+                       dev_warn(dmadev->ddev.dev, "channel did not stop\n");
+               mchan->paused = true;
+       }
+       pm_runtime_mark_last_busy(dmadev->ddev.dev);
+       pm_runtime_put_autosuspend(dmadev->ddev.dev);
+       return 0;
+}
+
+static int hidma_resume(struct dma_chan *chan)
+{
+       struct hidma_chan *mchan;
+       struct hidma_dev *dmadev;
+       int rc = 0;
+
+       mchan = to_hidma_chan(chan);
+       dmadev = to_hidma_dev(mchan->chan.device);
+       dev_dbg(dmadev->ddev.dev, "resume: chan:0x%p\n", mchan);
+
+       pm_runtime_get_sync(dmadev->ddev.dev);

Ditto.


I'll do the samething as pause.

+       if (mchan->paused) {
+               rc = hidma_ll_resume(dmadev->lldev);
+               if (!rc)
+                       mchan->paused = false;
+               else
+                       dev_err(dmadev->ddev.dev,
+                                       "failed to resume the channel");
+       }
+       pm_runtime_mark_last_busy(dmadev->ddev.dev);
+       pm_runtime_put_autosuspend(dmadev->ddev.dev);
+       return rc;
+}
+
+static irqreturn_t hidma_chirq_handler(int chirq, void *arg)
+{
+       struct hidma_lldev **lldev_ptr = arg;
+       irqreturn_t ret;
+       struct hidma_dev *dmadev = to_hidma_dev_from_lldev(lldev_ptr);
+
+       pm_runtime_get_sync(dmadev->ddev.dev);

Hmm... Do you have shared IRQ line or wakeup able one?
Otherwise I can't see ways how device can generate interrupts.
If there is a case other than described, put comment why it might happen.


All interrupts are request driven. HW doesn't send an interrupt by itself. I'll put some comment in the code.

+       ret = hidma_ll_inthandler(chirq, *lldev_ptr);
+       pm_runtime_mark_last_busy(dmadev->ddev.dev);
+       pm_runtime_put_autosuspend(dmadev->ddev.dev);
+       return ret;
+}
+
+static int hidma_probe(struct platform_device *pdev)
+{
+       struct hidma_dev *dmadev;
+       int rc = 0;
+       struct resource *trca_resource;
+       struct resource *evca_resource;
+       int chirq;
+       int current_channel_index = atomic_read(&channel_ref_count);
+
+       pm_runtime_set_autosuspend_delay(&pdev->dev, AUTOSUSPEND_TIMEOUT);
+       pm_runtime_use_autosuspend(&pdev->dev);
+       pm_runtime_set_active(&pdev->dev);
+       pm_runtime_enable(&pdev->dev);
+
+       trca_resource = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       if (!trca_resource) {
+               rc = -ENODEV;
+               goto bailout;
+       }
+
+       evca_resource = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+       if (!evca_resource) {
+               rc = -ENODEV;
+               goto bailout;
+       }


Consolidate these with devm_ioremap_resource();


ok

+
+       /* This driver only handles the channel IRQs.
+        * Common IRQ is handled by the management driver.
+        */
+       chirq = platform_get_irq(pdev, 0);
+       if (chirq < 0) {
+               rc = -ENODEV;
+               goto bailout;
+       }
+
+       dmadev = devm_kzalloc(&pdev->dev, sizeof(*dmadev), GFP_KERNEL);
+       if (!dmadev) {
+               rc = -ENOMEM;
+               goto bailout;
+       }
+
+       INIT_LIST_HEAD(&dmadev->ddev.channels);
+       spin_lock_init(&dmadev->lock);
+       dmadev->ddev.dev = &pdev->dev;
+       pm_runtime_get_sync(dmadev->ddev.dev);
+
+       dma_cap_set(DMA_MEMCPY, dmadev->ddev.cap_mask);
+       if (WARN_ON(!pdev->dev.dma_mask)) {
+               rc = -ENXIO;
+               goto dmafree;
+       }
+
+       dmadev->dev_evca = devm_ioremap_resource(&pdev->dev,
+                                               evca_resource);
+       if (IS_ERR(dmadev->dev_evca)) {
+               rc = -ENOMEM;
+               goto dmafree;
+       }
+
+       dmadev->dev_trca = devm_ioremap_resource(&pdev->dev,
+                                               trca_resource);
+       if (IS_ERR(dmadev->dev_trca)) {
+               rc = -ENOMEM;
+               goto dmafree;
+       }
+       dmadev->ddev.device_prep_dma_memcpy = hidma_prep_dma_memcpy;
+       dmadev->ddev.device_alloc_chan_resources =
+               hidma_alloc_chan_resources;
+       dmadev->ddev.device_free_chan_resources = hidma_free_chan_resources;
+       dmadev->ddev.device_tx_status = hidma_tx_status;
+       dmadev->ddev.device_issue_pending = hidma_issue_pending;
+       dmadev->ddev.device_pause = hidma_pause;
+       dmadev->ddev.device_resume = hidma_resume;
+       dmadev->ddev.device_terminate_all = hidma_terminate_all;
+       dmadev->ddev.copy_align = 8;
+
+       device_property_read_u32(&pdev->dev, "desc-count",
+                               &dmadev->nr_descriptors);
+
+       if (!dmadev->nr_descriptors && nr_desc_prm)
+               dmadev->nr_descriptors = nr_desc_prm;
+
+       if (!dmadev->nr_descriptors)
+               goto dmafree;
+
+       if (current_channel_index > MAX_HIDMA_CHANNELS)
+               goto dmafree;
+
+       dmadev->evridx = -1;
+       device_property_read_u32(&pdev->dev, "event-channel", &dmadev->evridx);
+
+       /* kernel command line override for the guest machine */
+       if (event_channel_idx[current_channel_index] != -1)
+               dmadev->evridx = event_channel_idx[current_channel_index];
+
+       if (dmadev->evridx == -1)
+               goto dmafree;
+
+       /* Set DMA mask to 64 bits. */
+       rc = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
+       if (rc) {
+               dev_warn(&pdev->dev, "unable to set coherent mask to 64");
+               rc = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
+       }
+       if (rc)
+               goto dmafree;
+
+       dmadev->lldev = hidma_ll_init(dmadev->ddev.dev,
+                               dmadev->nr_descriptors, dmadev->dev_trca,
+                               dmadev->dev_evca, dmadev->evridx);
+       if (!dmadev->lldev) {
+               rc = -EPROBE_DEFER;
+               goto dmafree;
+       }
+
+       rc = devm_request_irq(&pdev->dev, chirq, hidma_chirq_handler, 0,
+                             "qcom-hidma", &dmadev->lldev);

Better to use request_irq().


why? I thought we favored managed functions over standalone functions in simplify the exit path.

+       if (rc)
+               goto uninit;
+
+       INIT_LIST_HEAD(&dmadev->ddev.channels);
+       rc = hidma_chan_init(dmadev, 0);
+       if (rc)
+               goto uninit;
+
+       rc = dma_selftest_memcpy(&dmadev->ddev);

Thanks for the review.

--
Sinan Kaya
Qualcomm Technologies, Inc. on behalf of Qualcomm Innovation Center, Inc.
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, a Linux Foundation Collaborative Project
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Device Tree Compilter]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux PCI Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Yosemite Backpacking]
  Powered by Linux