代码分析

对于linux的驱动代码来说,我们要从后往前分析:

/** OMAP7xx SPI 100k controller driver* Author: Fabrice Crohas <fcrohas@gmail.com>* from original omap1_mcspi driver** Copyright (C) 2005, 2006 Nokia Corporation* Author:      Samuel Ortiz <samuel.ortiz@nokia.com> and*              Juha Yrj�l� <juha.yrjola@nokia.com>** This program is free software; you can redistribute it and/or modify* it under the terms of the GNU General Public License as published by* the Free Software Foundation; either version 2 of the License, or* (at your option) any later version.** This program is distributed in the hope that it will be useful,* but WITHOUT ANY WARRANTY; without even the implied warranty of* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the* GNU General Public License for more details.** You should have received a copy of the GNU General Public License* along with this program; if not, write to the Free Software* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA**/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/err.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/gpio.h>
#include <linux/slab.h>#include <linux/spi/spi.h>#include <plat/clock.h>#define OMAP1_SPI100K_MAX_FREQ          48000000#define ICR_SPITAS      (OMAP7XX_ICR_BASE + 0x12)#define SPI_SETUP1      0x00
#define SPI_SETUP2      0x02
#define SPI_CTRL        0x04
#define SPI_STATUS      0x06
#define SPI_TX_LSB      0x08
#define SPI_TX_MSB      0x0a
#define SPI_RX_LSB      0x0c
#define SPI_RX_MSB      0x0e#define SPI_SETUP1_INT_READ_ENABLE      (1UL << 5)
#define SPI_SETUP1_INT_WRITE_ENABLE     (1UL << 4)
#define SPI_SETUP1_CLOCK_DIVISOR(x)     ((x) << 1)
#define SPI_SETUP1_CLOCK_ENABLE         (1UL << 0)#define SPI_SETUP2_ACTIVE_EDGE_FALLING  (0UL << 0)
#define SPI_SETUP2_ACTIVE_EDGE_RISING   (1UL << 0)
#define SPI_SETUP2_NEGATIVE_LEVEL       (0UL << 5)
#define SPI_SETUP2_POSITIVE_LEVEL       (1UL << 5)
#define SPI_SETUP2_LEVEL_TRIGGER        (0UL << 10)
#define SPI_SETUP2_EDGE_TRIGGER         (1UL << 10)#define SPI_CTRL_SEN(x)                 ((x) << 7)
#define SPI_CTRL_WORD_SIZE(x)           (((x) - 1) << 2)
#define SPI_CTRL_WR                     (1UL << 1)
#define SPI_CTRL_RD                     (1UL << 0)#define SPI_STATUS_WE                   (1UL << 1)
#define SPI_STATUS_RD                   (1UL << 0)#define WRITE 0
#define READ  1/* use PIO for small transfers, avoiding DMA setup/teardown overhead and* cache operations; better heuristics consider wordsize and bitrate.*/
#define DMA_MIN_BYTES                   8#define SPI_RUNNING    0
#define SPI_SHUTDOWN    1struct omap1_spi100k {struct work_struct      work;/* lock protects queue and registers */spinlock_t              lock;struct list_head        msg_queue;struct spi_master       *master;struct clk              *ick;struct clk              *fck;/* Virtual base address of the controller */void __iomem            *base;/* State of the SPI */unsigned int        state;
};struct omap1_spi100k_cs {void __iomem            *base;int                     word_len;
};static struct workqueue_struct *omap1_spi100k_wq;#define MOD_REG_BIT(val, mask, set) do { \if (set) \val |= mask; \else \val &= ~mask; \
} while (0)static void spi100k_enable_clock(struct spi_master *master)
{unsigned int val;struct omap1_spi100k *spi100k = spi_master_get_devdata(master);/* enable SPI */val = readw(spi100k->base + SPI_SETUP1);val |= SPI_SETUP1_CLOCK_ENABLE;writew(val, spi100k->base + SPI_SETUP1);
}static void spi100k_disable_clock(struct spi_master *master)
{unsigned int val;struct omap1_spi100k *spi100k = spi_master_get_devdata(master);/* disable SPI */val = readw(spi100k->base + SPI_SETUP1);val &= ~SPI_SETUP1_CLOCK_ENABLE;writew(val, spi100k->base + SPI_SETUP1);
}static void spi100k_write_data(struct spi_master *master, int len, int data)
{struct omap1_spi100k *spi100k = spi_master_get_devdata(master);/* write 16-bit word, shifting 8-bit data if necessary */if (len <= 8) {data <<= 8;len = 16;}spi100k_enable_clock(master);writew( data , spi100k->base + SPI_TX_MSB);writew(SPI_CTRL_SEN(0) |SPI_CTRL_WORD_SIZE(len) |SPI_CTRL_WR,spi100k->base + SPI_CTRL);/* Wait for bit ack send change */while((readw(spi100k->base + SPI_STATUS) & SPI_STATUS_WE) != SPI_STATUS_WE);udelay(1000);spi100k_disable_clock(master);
}static int spi100k_read_data(struct spi_master *master, int len)
{int dataH,dataL;struct omap1_spi100k *spi100k = spi_master_get_devdata(master);/* Always do at least 16 bits */if (len <= 8)len = 16;spi100k_enable_clock(master);writew(SPI_CTRL_SEN(0) |SPI_CTRL_WORD_SIZE(len) |SPI_CTRL_RD,spi100k->base + SPI_CTRL);while((readw(spi100k->base + SPI_STATUS) & SPI_STATUS_RD) != SPI_STATUS_RD);udelay(1000);dataL = readw(spi100k->base + SPI_RX_LSB);dataH = readw(spi100k->base + SPI_RX_MSB);spi100k_disable_clock(master);return dataL;
}static void spi100k_open(struct spi_master *master)
{/* get control of SPI */struct omap1_spi100k *spi100k = spi_master_get_devdata(master);writew(SPI_SETUP1_INT_READ_ENABLE |SPI_SETUP1_INT_WRITE_ENABLE |SPI_SETUP1_CLOCK_DIVISOR(0), spi100k->base + SPI_SETUP1);/* configure clock and interrupts */writew(SPI_SETUP2_ACTIVE_EDGE_FALLING |SPI_SETUP2_NEGATIVE_LEVEL |SPI_SETUP2_LEVEL_TRIGGER, spi100k->base + SPI_SETUP2);
}static void omap1_spi100k_force_cs(struct omap1_spi100k *spi100k, int enable)
{if (enable)writew(0x05fc, spi100k->base + SPI_CTRL);elsewritew(0x05fd, spi100k->base + SPI_CTRL);
}static unsigned
omap1_spi100k_txrx_pio(struct spi_device *spi, struct spi_transfer *xfer)
{struct omap1_spi100k    *spi100k;struct omap1_spi100k_cs *cs = spi->controller_state;unsigned int            count, c;int                     word_len;spi100k = spi_master_get_devdata(spi->master);count = xfer->len;c = count;word_len = cs->word_len;if (word_len <= 8) {u8              *rx;const u8        *tx;rx = xfer->rx_buf;tx = xfer->tx_buf;do {c-=1;if (xfer->tx_buf != NULL)spi100k_write_data(spi->master, word_len, *tx++);if (xfer->rx_buf != NULL)*rx++ = spi100k_read_data(spi->master, word_len);} while(c);} else if (word_len <= 16) {u16             *rx;const u16       *tx;rx = xfer->rx_buf;tx = xfer->tx_buf;do {c-=2;if (xfer->tx_buf != NULL)spi100k_write_data(spi->master,word_len, *tx++);if (xfer->rx_buf != NULL)*rx++ = spi100k_read_data(spi->master,word_len);} while(c);} else if (word_len <= 32) {u32             *rx;const u32       *tx;rx = xfer->rx_buf;tx = xfer->tx_buf;do {c-=4;if (xfer->tx_buf != NULL)spi100k_write_data(spi->master,word_len, *tx);if (xfer->rx_buf != NULL)*rx = spi100k_read_data(spi->master,word_len);} while(c);}return count - c;
}/* called only when no transfer is active to this device */
static int omap1_spi100k_setup_transfer(struct spi_device *spi,struct spi_transfer *t)
{struct omap1_spi100k *spi100k = spi_master_get_devdata(spi->master);struct omap1_spi100k_cs *cs = spi->controller_state;u8 word_len = spi->bits_per_word;if (t != NULL && t->bits_per_word)word_len = t->bits_per_word;if (!word_len)word_len = 8;if (spi->bits_per_word > 32)return -EINVAL;cs->word_len = word_len;/* SPI init before transfer */writew(0x3e , spi100k->base + SPI_SETUP1);writew(0x00 , spi100k->base + SPI_STATUS);writew(0x3e , spi100k->base + SPI_CTRL);return 0;
}/* the spi->mode bits understood by this driver: */
#define MODEBITS (SPI_CPOL | SPI_CPHA | SPI_CS_HIGH)static int omap1_spi100k_setup(struct spi_device *spi)
{int                     ret;struct omap1_spi100k    *spi100k;struct omap1_spi100k_cs *cs = spi->controller_state;if (spi->bits_per_word < 4 || spi->bits_per_word > 32) {dev_dbg(&spi->dev, "setup: unsupported %d bit words\n",spi->bits_per_word);return -EINVAL;}spi100k = spi_master_get_devdata(spi->master);if (!cs) {cs = kzalloc(sizeof *cs, GFP_KERNEL);if (!cs)return -ENOMEM;cs->base = spi100k->base + spi->chip_select * 0x14;spi->controller_state = cs;}spi100k_open(spi->master);clk_enable(spi100k->ick);clk_enable(spi100k->fck);ret = omap1_spi100k_setup_transfer(spi, NULL);clk_disable(spi100k->ick);clk_disable(spi100k->fck);return ret;
}
// 信息传输的工作就在这个函数执行啦,这里每次传输一次,就使能一次
static void omap1_spi100k_work(struct work_struct *work)
{struct omap1_spi100k    *spi100k;int status = 0;spi100k = container_of(work, struct omap1_spi100k, work);spin_lock_irq(&spi100k->lock);clk_enable(spi100k->ick);clk_enable(spi100k->fck);/* We only enable one channel at a time -- the one whose message is* at the head of the queue -- although this controller would gladly* arbitrate among multiple channels.  This corresponds to "single* channel" master mode.  As a side effect, we need to manage the* chipselect with the FORCE bit ... CS != channel enable.*/while (!list_empty(&spi100k->msg_queue)) {struct spi_message              *m;struct spi_device               *spi;struct spi_transfer             *t = NULL;int                             cs_active = 0;struct omap1_spi100k_cs         *cs;int                             par_override = 0;m = container_of(spi100k->msg_queue.next, struct spi_message,queue);list_del_init(&m->queue);spin_unlock_irq(&spi100k->lock);spi = m->spi;cs = spi->controller_state;list_for_each_entry(t, &m->transfers, transfer_list) {if (t->tx_buf == NULL && t->rx_buf == NULL && t->len) {status = -EINVAL;break;}if (par_override || t->speed_hz || t->bits_per_word) {par_override = 1;status = omap1_spi100k_setup_transfer(spi, t);if (status < 0)break;if (!t->speed_hz && !t->bits_per_word)par_override = 0;}if (!cs_active) {omap1_spi100k_force_cs(spi100k, 1);cs_active = 1;}if (t->len) {unsigned count;count = omap1_spi100k_txrx_pio(spi, t);m->actual_length += count;if (count != t->len) {status = -EIO;break;}}if (t->delay_usecs)udelay(t->delay_usecs);/* ignore the "leave it on after last xfer" hint */if (t->cs_change) {omap1_spi100k_force_cs(spi100k, 0);cs_active = 0;}}/* Restore defaults if they were overriden */if (par_override) {par_override = 0;status = omap1_spi100k_setup_transfer(spi, NULL);}if (cs_active)omap1_spi100k_force_cs(spi100k, 0);m->status = status;m->complete(m->context);spin_lock_irq(&spi100k->lock);}clk_disable(spi100k->ick);clk_disable(spi100k->fck);spin_unlock_irq(&spi100k->lock);if (status < 0)printk(KERN_WARNING "spi transfer failed with %d\n", status);
}// 这个函数就是在驱动设备已经绑定好后开始进行传输使用的函数,下面我们来分析。
// 从函数参数来看,有2个参数,一个是设备参数指针,另一个是信息流指针,从这里我们可以
// 看出调用此函数是为了向spi这个设备传送m这个信息流。
static int omap1_spi100k_transfer(struct spi_device *spi, struct spi_message *m)
{struct omap1_spi100k    *spi100k;unsigned long           flags;struct spi_transfer     *t;m->actual_length = 0;m->status = -EINPROGRESS;// 从spi主驱动获取设备数据spi100k = spi_master_get_devdata(spi->master);/* Don't accept new work if we're shutting down */// 这个人家也说了,在进行退出模块的过程的中如果有传输的任务的话,我们// 就不再传输了,直接退出。if (spi100k->state == SPI_SHUTDOWN)return -ESHUTDOWN;/* reject invalid messages and transfers */// 这段代码的意思呢就是当该设备没有完整的准备传输数据时,就不再进行传输了。if (list_empty(&m->transfers) || !m->complete)return -EINVAL;// 这段代码就是传输代码的检测步骤,目的是为了检测当前通信频率是否符合要求。list_for_each_entry(t, &m->transfers, transfer_list) {const void      *tx_buf = t->tx_buf;void            *rx_buf = t->rx_buf;unsigned        len = t->len;if (t->speed_hz > OMAP1_SPI100K_MAX_FREQ|| (len && !(rx_buf || tx_buf))|| (t->bits_per_word &&(  t->bits_per_word < 4|| t->bits_per_word > 32))) {dev_dbg(&spi->dev, "transfer: %d Hz, %d %s%s, %d bpw\n",t->speed_hz,len,tx_buf ? "tx" : "",rx_buf ? "rx" : "",t->bits_per_word);return -EINVAL;}if (t->speed_hz && t->speed_hz < OMAP1_SPI100K_MAX_FREQ/(1<<16)) {dev_dbg(&spi->dev, "%d Hz max exceeds %d\n",t->speed_hz,OMAP1_SPI100K_MAX_FREQ/(1<<16));return -EINVAL;}}// 这段代码就是将m的信息流加到该设备的传输信息流链表中,// 然后传输的工作在另一个线程中omap1_spi100k_wq执行。spin_lock_irqsave(&spi100k->lock, flags);list_add_tail(&m->queue, &spi100k->msg_queue);queue_work(omap1_spi100k_wq, &spi100k->work);spin_unlock_irqrestore(&spi100k->lock, flags);return 0;
}static int __init omap1_spi100k_reset(struct omap1_spi100k *spi100k)
{return 0;
}// 线程的探测函数,当探测成后即刻进行初始化该设备操作。
static int __devinit omap1_spi100k_probe(struct platform_device *pdev)
{struct spi_master       *master;struct omap1_spi100k    *spi100k;int                     status = 0;// 设备的id不能为0,要么为-1,要么为其他正数,表示同一类设备的唯一性或枚举性。if (!pdev->id)return -EINVAL;// 给一个平台设备,然后申请一个spi的驱动框架。master = spi_alloc_master(&pdev->dev, sizeof *spi100k);if (master == NULL) {dev_dbg(&pdev->dev, "master allocation failed\n");return -ENOMEM;}if (pdev->id != -1)master->bus_num = pdev->id;// 填充刚申请的spi驱动框架master->setup = omap1_spi100k_setup;master->transfer = omap1_spi100k_transfer;master->cleanup = NULL;master->num_chipselect = 2;master->mode_bits = MODEBITS;// 将该驱动框架的结构体对象设置到该设备中,用与内核的使用dev_set_drvdata(&pdev->dev, master);// 从spi的驱动框架中获取设备数据,这个设备数据就是该模块的中定义的结构体中的数据spi100k = spi_master_get_devdata(master);spi100k->master = master;/** The memory region base address is taken as the platform_data.* You should allocate this with ioremap() before initializing* the SPI.*/// 这个平台数据就是设备中本身携带的数据了,一般这些信息可以通过设备树传输spi100k->base = (void __iomem *) pdev->dev.platform_data;INIT_WORK(&spi100k->work, omap1_spi100k_work);spin_lock_init(&spi100k->lock);INIT_LIST_HEAD(&spi100k->msg_queue);// 获取时钟源spi100k->ick = clk_get(&pdev->dev, "ick");if (IS_ERR(spi100k->ick)) {dev_dbg(&pdev->dev, "can't get spi100k_ick\n");status = PTR_ERR(spi100k->ick);goto err1;}spi100k->fck = clk_get(&pdev->dev, "fck");if (IS_ERR(spi100k->fck)) {dev_dbg(&pdev->dev, "can't get spi100k_fck\n");status = PTR_ERR(spi100k->fck);goto err2;}// 复位该设备,一般在使用该设备之前都要进行复位一次。if (omap1_spi100k_reset(spi100k) < 0)goto err3;// 开始注册该设备驱动status = spi_register_master(master);if (status < 0)goto err3;spi100k->state = SPI_RUNNING;return status;err3:clk_put(spi100k->fck);
err2:clk_put(spi100k->ick);
err1:spi_master_put(master);return status;
}// 当该模块退出时,我们会调用这个函数,进行移除该模块原来在系统中占用的空间
// 下面我们来分析这个移除模块的移除步骤。
static int __exit omap1_spi100k_remove(struct platform_device *pdev)
{struct spi_master       *master;struct omap1_spi100k    *spi100k;struct resource         *r;unsigned       limit = 500;unsigned long      flags;int           status = 0;// 从设备获取我们的驱动信息master = dev_get_drvdata(&pdev->dev);// 从驱动信息获取我们的设备数据spi100k = spi_master_get_devdata(master);// 自旋锁加锁,并关中断,进行一些短的不被除此之外鹅任何进程打扰到的代码片段spin_lock_irqsave(&spi100k->lock, flags);// 改变该设备的标志位信息,使其为关闭状态spi100k->state = SPI_SHUTDOWN;// 这段代码的意思是尽可能的的等待该设备的还存在的信息进行处理完成,// 因为马上要关闭模块了。while (!list_empty(&spi100k->msg_queue) && limit--) {spin_unlock_irqrestore(&spi100k->lock, flags);msleep(10);spin_lock_irqsave(&spi100k->lock, flags);}// 这个就是判断设备的信息队列中是否还有数据,如果进行了之前的操作仍然不能处理完设备// 中的信息队列,那么就显示设备状态为忙状态,不再进行此次的退出操作。if (!list_empty(&spi100k->msg_queue))status = -EBUSY;// 释放自旋锁,恢复中断,目的是为了保护这加锁期间的代码spin_unlock_irqrestore(&spi100k->lock, flags);if (status != 0)return status;// 这里就是释放用于spi通信使用的时钟源,注意// clk_put函数不能在中断上下文中使用,具体为什么,可以看源码clk_put(spi100k->fck);clk_put(spi100k->ick);// 这个时候应该对资源进行一些处理的,但这里并没有处理,只是获取而已。r = platform_get_resource(pdev, IORESOURCE_MEM, 0);// 调用该函数注销这个spi主设备的驱动。spi_unregister_master(master);return 0;
}// 我们的驱动结构体,我们需要在这个结构体中填充关于该驱动的写必要的信息。
// 一般情况下,我们至少要填充平台驱动下的设备驱动变量中的名称name和所属者owener,
// 这是设备驱动中需要的最小信息,之后我们要填充平台驱动下的移除函数remove,填写
// 这个函数为了卸载该模块使用的,至于平台驱动下的探测函数会在初始话函数中进行填充。
static struct platform_driver omap1_spi100k_driver = {.driver = {.name        = "omap1_spi100k",.owner     = THIS_MODULE,},.remove        = __exit_p(omap1_spi100k_remove),
};static int __init omap1_spi100k_init(void)
{// create a single thread work queue,// we must free the work queue when exit the driver.// the purpose of creating a single thread is// to excute the @spi100k->work function.omap1_spi100k_wq = create_singlethread_workqueue(omap1_spi100k_driver.driver.name);if (omap1_spi100k_wq == NULL)return -1;// 该处初始化函数调用平台驱动探测函数,会传入2个参数,一个是我们该模块的驱动信息,// 另一个是该模块的探测函数。执行这个接口的目的是为了将该模块的驱动信息与已经在系统// 注册的设备信息进行绑定,然后在该模块探测函数中初始化一些该模块必要// 初始化步骤,用来支持该模块的工作。该模块就是通过spi进行通信的。至于系统怎么// 绑定设备与驱动的,感兴趣的可以分析下源码,这里不再分析。return platform_driver_probe(&omap1_spi100k_driver, omap1_spi100k_probe);
}static void __exit omap1_spi100k_exit(void)
{// 平台驱动注销函数接口,为什么会有这个接口呢?就是因为在该模块初始化过程中,// 调用了平台驱动注册函数,其函数内部因为有申请堆空间或者加载链表信息等操作,// 所以,在退出的时候,一定要归还这些系统中的位置。platform_driver_unregister(&omap1_spi100k_driver);// 一样的解释,前面初始化申请了一个单线程的工作队列用于处理该驱动的通信工作,// 当退出时,一定要把这个申请的工作队列释放掉,为系统腾出空间。destroy_workqueue(omap1_spi100k_wq);
}// 模块的加载入口,当初始化该模块或者注册该模块时,系统会执行omap1_spi100k_init该
// 函数,进行注册一些驱动运行需要的信息,并且与设备的信息进行绑定,
// 形成完成的驱动的初始化流程.
module_init(omap1_spi100k_init);// 模块的卸载入口,当不再使用模块时,内核代码会调用该接口并执行omap1_spi100k_exit
// 函数,释放一些申请的内存空间,然后从运行的系统中删除自己的一些信息。
module_exit(omap1_spi100k_exit);// 模块的作者信息,和一些描述信息。让人们知道该模块的基本信息
MODULE_DESCRIPTION("OMAP7xx SPI 100k controller driver");
MODULE_AUTHOR("Fabrice Crohas <fcrohas@gmail.com>");// 模块遵循的许可证,对于我们驱动开发来说,一般写成GPL就可以
MODULE_LICENSE("GPL");

总结

从上面的代码分析来看,驱动最关键的部分就是探测函数和工作函数了,它们一个负责绑定初始化设备驱动,另一个负责驱动通信的主体工作,其他函数就是围绕它们来展开的。

linux驱动开发之spi-omap-100k.c源码分析相关推荐

  1. Android开发之无bug滑动删除源码(非第三方库)

    Android开发之无bug滑动删除源码(非第三方库源码请在最后面自行下载) 1.我们先来看下效果图:上边是抽取出来的demo,下边是公司用到的项目 2.我们来看下如何调用(我们这里以listView ...

  2. Linux驱动开发之USB驱动深入学习(三)——USB2.0ECHI驱动注册

    一.前言 本篇博客仅对ECHI主机控制器驱动的注册部分进行简要叙述,后面再对一些重要的接口进行分析讲解. 二.USB 1.概述 USB(Universal Serial Bus)即"通用外部 ...

  3. SDIO_WiFi驱动学习之SDIO架构介绍及源码分析

    一.引言 因为WiFi驱动比较复杂,所以WiFi驱动的博客将多分几篇来写. 本篇博客主要介绍Linux下的SDIO架构及源码分析. 本文部分内容摘抄自网络,若有侵权,请联系删除. 二.SDIO WiF ...

  4. ㉔AW-H3 Linux驱动开发之HDMI驱动程序

    HDMI: High Definition Multimedia Interface,高清多媒体接口,是一种全数字化视频和声音发送接口,可以发送未压缩的音频及视频信号.HDMI有4种类型的接口,分别为 ...

  5. ⑨tiny4412 Linux驱动开发之1-wire子系统(DS18B20)驱动程序

    本来这次想做LCD背光灯的调节的,但是没有调通,时间很紧迫,就转向了其它东西,昨天调了一下DHT11,今天又调了一下DS18B20,还算有个安慰,本来是想用1-wire子系统做的,但是时间上有点紧,要 ...

  6. ㉕AW-A33 Linux驱动开发之audio子系统驱动程序

    在Linux源码里,Aduio这一部分现在是一个独立文件夹叫sound,在2.x的版本时,sound这个目录是在drivers里的,后来从这个里面剥离出来了,很多人不知道其中的原因,我也不知道,我们先 ...

  7. Linux驱动开发之platform设备驱动实验【完整教程】

    为了方便驱动的编写,提高软件的重用性和跨平台性能,于是就提出了Linux驱动的分离和分层   驱动的分层,分层的目的时为了在不同的层处理不同的内容,最简单的驱动分层是input子系统负责管理所有跟输入 ...

  8. linux编译input驱动,Linux驱动开发之input子系统

    本文对mousedev.Amimouse和input子系统进行分析,旨在提纲挈领,给出它们之间的调用关系(或者说关联).阅读本文,需要与阅读Linux 2.6内核源码交叉进行,除非你是超人. 背景: ...

  9. 【Linux 内核】进程管理 ( 进程相关系统调用源码分析 | fork() 源码 | vfork() 源码 | clone() 源码 | _do_fork() 源码 | do_fork() 源码 )

    文章目录 一.fork 系统调用源码 二.vfork 系统调用源码 三.clone 系统调用源码 四._do_fork 函数源码 五.do_fork 函数源码 Linux 进程相关 " 系统 ...

最新文章

  1. 如果你要对一个变量进行反向传播,你必须保证其为Tensor
  2. python中is与==的差别
  3. Android 国际化
  4. C语言学习之输入一行字符,分别统计出其中英文字母、空格、数字和其他字符的个数。
  5. 基于顺序存储结构的图书信息表的逆序存储(C++)
  6. 想学java编程从哪入手_初学编程从哪方面入手?
  7. PIC16F877A开发板 数码管计数器实验
  8. python爬虫--requests模块
  9. Visual Studio 创建C语言项目
  10. Java语言开发的开源商城系统——Javashop简介
  11. 发那科机器人示教器键盘_不限 发那科机器人示教器触摸屏急停按键失效维修...
  12. 北邮iptv用WindowsMediaplayer打不开的解决的方法
  13. 利用Python爬取拉勾网招聘信息
  14. VScode 常用插件推荐
  15. 【命名规则】驼峰命名法
  16. 微机原理与接口技术复习笔记(1)——微型计算机概述
  17. 图片转word表格在线教学,想知道图片转word表格怎么转吗?
  18. 利用R语言制作GGEBiplot-双标图教程
  19. 程序员孙某三年白干:因违反腾讯《竞业协议》赔 97.6 万元,返还 15.8 万元
  20. 【云豹直播系统】专业制作仿映客手机直播app、视频直播系统

热门文章

  1. 什么是哥德尔不完备定理?
  2. 快传号可以搬运吗,快传号搬运哪些领域容易爆文
  3. VM使用-pin针同心度检测
  4. 敏捷开发“松结对编程”实践之三:共同估算篇(大型研发团队,学习型团队,139团队,师徒制度,敏捷设计,估算扑克,扑克牌估算) .
  5. bootstrap-列表样式
  6. PC端 UC浏览器页面显示该站点安全证书的吊销不可用
  7. MySQL求百分比带百分号%
  8. 深度操作系统 15.7 —— 性能好才是真的好
  9. win10桌面右下角网络图标中找不到网络
  10. JQuery插件之-----Datatables(三)Datatables实现多选框与AJAX返回数据