首先,我不是做驱动的开发人员。所以只能用自娱自乐来表示我的行为。

我不知道udc和gadget驱动是不是冷门的驱动,资料真是不多。我之前买了一本书,上面说到这些,就教你如何调试已写好的驱动。这样也可以写书,太坑了吧!我随便从网上搜搜都能写的比他好。难道现在的育人机构为了钱都变成了坑人机构。

我以前就希望把自己写过的驱动总结成一个模板,让人能直观的看出linux提供的接口要我们做什么甚至怎么做。虽然做这个比较难,但我还是成功的欺骗了自己,可以做到。

这是自娱自乐第一期,可能废话多了一点,请大家原谅。现在说这个模板。这个是一个未做实际应用的模板,只是编译通过,除了没实践,还缺少DMA和USB的请求类型处理样例。后期我会用它做一个驱动,不断的完善。现在这个应该在理论和实践之间的东西。

常用结构体(别人写的,不是linux-3.2.36,不过差不多)

========================================================USB UDC与gadget驱动=========================================================
/*
*linux内核中usb设备侧驱动程序分为3个层次:UDC驱动、Gadget API和Gadget驱动程序,UDC驱动程序直接访问硬件usb控制器OHCI/EHCI/UHCI,作为usb设备和主机间的底层通信,向上层
*提供与硬件相关操作的回调函数。当前Gadget API是对UDC驱动程序回调函数的简单包装。Gadget驱动程序具体控制系统作为usb设备时的相关功能的实现,使设备表现出“网络连接”、“打印机”
*或“USB Mass Storage”等特性。
*
* 这里的USB设备控制器(UDC)驱动指作为其他USB主机控制器外设的USB硬件设备上底层硬件控制器的驱动,该硬件和驱动负责将USB设备依附于一个USB主机控制器上:比如当某运行linux的手机作为PC
*的U盘时,手机的底层USB控制器行使USB设备控制器的功能,这时候运行在底层的是UDC驱动,而手机成为U盘,在UDC驱动之上仍然需要另一个驱动,对于USB大容量存储器为file storage驱动,这一
*驱动称为gadget驱动(总之是一个运行linux的系统的usb接口作为另一个linux系统的设备)。usb设备驱动调用usb核心提供的API,因此具体驱动与SOC无关。同样,usb gadget驱动调用通用的gadget API
*因此具体gadget驱动也变得与SOC无关。
*UDC驱动和gadget驱动都位于内核的drivers/usb/gadget目录下,S3C2410对应的UDC驱动为s3c2410_udc.c。ether.c、f_serial.c、file_storage.c等文件实现了一些gadget驱动
*/
#include <linux/gadget.h> -----------------------------------------------------------struct usb_gadget------------------------------------------------------
struct usb_gadget { //描述USB设备控制器
/* readonly to gadget driver */ //针对gadget驱动只读
const struct usb_gadget_ops *ops; //访问硬件函数
struct usb_ep *ep0; //端点0,setup使用
struct list_head ep_list; /* of usb_ep */ //其他端点列表
enum usb_device_speed speed;
unsigned is_dualspeed:1;
unsigned is_otg:1;
unsigned is_a_peripheral:1;
unsigned b_hnp_enable:1; //A-HOST使能了HNP支持
unsigned a_hnp_support:1; //A-HOST支持HNP
unsigned a_alt_hnp_support:1;
const char *name;
struct device dev;
}; ------------------------------------------------------struct usb_gadget_ops-------------------------------------------------------
struct usb_gadget_ops { //硬件操作函数
int (*get_frame)(struct usb_gadget *);
int (*wakeup)(struct usb_gadget *);
int (*set_selfpowered) (struct usb_gadget *, int is_selfpowered);
int (*vbus_session) (struct usb_gadget *, int is_active);
int (*vbus_draw) (struct usb_gadget *, unsigned mA);
int (*pullup) (struct usb_gadget *, int is_on);
int (*ioctl)(struct usb_gadget *,
unsigned code, unsigned long param);
}; -----------------------------------------------------struct usb_gadget_driver----------------------------------------------------
struct usb_gadget_driver { //描述gadget驱动
char *function; //描述gadget功能的字符串
enum usb_device_speed speed;
int (*bind)(struct usb_gadget *); //当驱动和gadget绑定时调用
void (*unbind)(struct usb_gadget *);
int (*setup)(struct usb_gadget *, //处理硬件驱动未处理的端点0请求
const struct usb_ctrlrequest *);
void (*disconnect)(struct usb_gadget *);
void (*suspend)(struct usb_gadget *);
void (*resume)(struct usb_gadget *); /* FIXME support safe rmmod */
struct device_driver driver;
}; -----------------------------------------------------struct usb_request----------------------------------------------------------
struct usb_request { //表示一个传输请求的usb_request(与从机端看到的urb相似)
void *buf;
unsigned length;
dma_addr_t dma; unsigned no_interrupt:1;
unsigned zero:1;
unsigned short_not_ok:1; void (*complete)(struct usb_ep *ep,
struct usb_request *req);
void *context;
struct list_head list; int status;
unsigned actual;
}; ----------------------------------------------------------struct usb_ep---------------------------------------------------------
struct usb_ep { //描述一个端点
void *driver_data; const char *name;
const struct usb_ep_ops *ops;
struct list_head ep_list;
unsigned maxpacket:16;
}; ------------------------------------------------------struct usb_ep_ops---------------------------------------------------------
struct usb_ep_ops { //描述端点操作
int (*enable) (struct usb_ep *ep,
const struct usb_endpoint_descriptor *desc);
int (*disable) (struct usb_ep *ep); struct usb_request *(*alloc_request) (struct usb_ep *ep,
gfp_t gfp_flags);
void (*free_request) (struct usb_ep *ep, struct usb_request *req); int (*queue) (struct usb_ep *ep, struct usb_request *req,
gfp_t gfp_flags);
int (*dequeue) (struct usb_ep *ep, struct usb_request *req); int (*set_halt) (struct usb_ep *ep, int value);
int (*set_wedge) (struct usb_ep *ep); int (*fifo_status) (struct usb_ep *ep);
void (*fifo_flush) (struct usb_ep *ep);
};
--------------------------------------------------------------------------------------------------------------------------------
/*
*UDC和gadget驱动围绕上述数据结构及其成员函数而展开。
*在具体的UDC驱动中,需要封装usb_gadget和每个端点usb_ep,实现端点usb_ep_ops,完成usb_request。另外usb_gadget_register_driver和usb_gadget_unregister_driver这两个API需要由UDC
*驱动提供,gadget驱动会调用它们。
*/
int usb_gadget_register_driver(struct usb_gadget_driver *driver); //注册,在加载模块中调用,该函数中会调用driver->bind()函数,将usb_gadget_driver与具体的gadget绑定
int usb_gadget_unregister_driver(struct usb_gadget_driver *driver); //注销,在卸载模块中调用,告诉UDC驱动不再投入工作,如果UDC正在和USB主机连接,会先调用driver->disconnect()
//函数,而后会调用unbind()函数
//在linux/usb/gadget.h中,封装了一些常用的API:
int usb_ep_enable(struct usb_ep *ep,const struct usb_endpoint_descriptor *desc); //使能端点 ,该函数会调用struct usb_ep_ops->enable()
int usb_ep_disable(struct usb_ep *ep); //禁止端点,该函数会调用struct usb_ep_ops->disable() struct usb_request *usb_ep_alloc_request(struct usb_ep *ep,gfp_t gfp_flags); //分配一个依附于某端点的 usb_request,该函数会调用struct usb_ep_ops->usb_request()
void usb_ep_free_request(struct usb_ep *ep,struct usb_request *req); //释放一个依附于某端点的 usb_request,该函数会调用struct usb_ep_ops->free_request() int usb_ep_queue(struct usb_ep *ep, struct usb_request *req, gfp_t gfp_flags);//提交usb_request,该函数告诉UDC完成usb_request(读写buffer),当请求被完成后,该请求对应的completion函数会被调用,
//该函数会调用struct usb_ep_ops->queue(),该函数告诉UDC完成usb_request(读写buffer),当请求完成后,该请求对应的completion函数会被调用
int usb_ep_dequeue(struct usb_ep *ep, struct usb_request *req); //取消usb_request,该函数会调用struct usb_ep_ops->dequeue() 端点FIFO管理:
int usb_ep_fifo_status(struct usb_ep *ep); //该函数会调用truct usb_ep_ops->fifo_status返回目前FIFO中的字节数
void usb_ep_fifo_flush(struct usb_ep *ep); //该函数会调用truct usb_ep_ops->fifo_flush,以flush(冲洗)掉FIFO中的数据 int usb_gadget_frame_number(struct usb_gadget *gadget); //它调用gadget->ops->get_frame(gadget)返回目前的帧号 /*
*S3C2410的UDC驱动在 /driver/usb/gadget/s3c2410_udc.c
*/
/-----------------------------------------------------------------------------------------------------------------------------/

看请求队列的处理

struct xxxxx_request

{

structlist_head        queue;        /* ep'srequests */

structusb_request        req;        //对应主机端看到的urb

};

struct xxxxx_ep

{

struct usb_ep ep; //描述一个端点

struct list_head queue;

}

/*device/ep0 records init */

INIT_LIST_HEAD(&dev->gadget.ep_list);

INIT_LIST_HEAD(&dev->gadget.ep0->ep_list);

/* basic endpoint recordsinit */

for(i = 0; i < XXXXX_ENDPOINTS; i++) {

structxxxxx_ep *ep = &dev->ep[i];

if(i != 0)//除了ep0

list_add_tail(&ep->ep.ep_list, &dev->gadget.ep_list);

INIT_LIST_HEAD(&ep->queue);

}

ep0就用gadget里面的,这个在上层的gadget驱动才能看到。Udc只要这样做就可以了

gadget驱动会调用xxxxx_udc_queue把请求插入ep->queue

udc在端点中断时会从对应的queue取出处理,我的模板没体现这点。

下面说udc驱动大概包涵哪些。

1.       struct usb_ep_ops

2.       struct usb_gadget_ops xxxxx_ops

3.       有一个中断来处理对应的事件,请求一般包涵在次。(目前的模板没有体现)。

事实上现在的模板就是告诉你上面两个要实现什么,及怎么实现。代码中有详细解释。我不多说了。

.H

/***********************************
Copyright(C), 2013 LDP
FileName: xxxxx_udc.h
Author: wwxxxxll
Date:
Description:
History:
Author Date Desc
************************************/
#ifndef __XXXXX_UDC_H__
#define __XXXXX_UDC_H__
/*************配置选项**************/
#define XXXXX_DEBUG_FS //使用debugfs //struct usb_ep_ops
#define XXXXX_NEWSTYLE //使用udc_start
#define XXXXX_SETWEDHE //实现set_weght方法
#define XXXXX_FIFO_STATUS //支持fifo_status方法
#define XXXXX_FIFO_FLUSH //支持fifo_flush方法 //struct usb_gadget_ops
#define XXXXX_XXXXX_GET_FRAME //支持get_frame
#define XXXXX_WAKEUP //支持wakeup功能
#define XXXXX_SELFPOWERED //selfpowered支持
#define XXXXX_VBUS_SESSION //vbus连接控制支持
#define XXXXXX_PULLUP //usb连接控制支持 #define XXXXX_HAVE_CLK //有专用的CLK
#ifdef XXXXX_HAVE_CLK
#define CLK_DELAY_TIME 10 //ms
#endif #define XXXXX_USE_IRQ //端口信息
#define XXXXX_ENDPOINTS 2 //端口数
#define EP0_FIFO_SIZE 8
#define EP1_FIFO_SIZE 64
#define EP1_ADDRESS 1
#define EP1_ATTR USB_ENDPOINT_XFER_BULK
#define XXXXX_EP_FILO_SIZE 128
/***********************************/ /*************寄存器定义************/ /***********************************/ struct xxxxx_ep
{
struct usb_ep ep; //描述一个端点
struct list_head queue;
struct xxxxx_udc *dev;
const struct usb_endpoint_descriptor *desc; unsigned char fifosize;
unsigned char bEndpointAddress;
unsigned char bmAttributes; u16 fifo_size;
u8 num; unsigned stopped :1;//维护一个端口停止标志 #ifdef XXXXX_SETWEDHE
unsigned wedged :1;
#endif
}; #define to_xxxxx_ep(ep_p) container_of(ep_p, struct xxxxx_ep, ep) struct xxxxx_request
{
struct list_head queue; /* ep's requests */
struct usb_request req; //对应主机端看到的urb
}; #define to_xxxxx_req(req_p) container_of(req_p, struct xxxxx_request, req) //根据实际要求定义,这个不能当做模板,主要是便于软件管理
//一般有下面几个,有的驱动不用这些定义去管理
enum ep0state {
EP0_IDLE,
EP0_IN,
EP0_OUT,
EP0_STALL,
}; struct xxxxx_udc
{
spinlock_t lock; void __iomem *virl_addr;
u32 phy_addr;
u32 reg_size; struct usb_gadget gadget;
struct usb_gadget_driver *driver; enum ep0state ep0state;
struct xxxxx_ep ep[XXXXX_ENDPOINTS];
struct xxxxx_request fifo_req; #ifdef XXXXX_DEBUG_FS
struct dentry *debug_info;
#endif #ifdef XXXXX_HAVE_CLK
struct clk *xxxxx_clk;
#endif #ifdef XXXXX_USE_IRQ
unsigned int irq_num;
#endif u16 devstatus;
}; #define to_xxxxx_udc(gadget_p) container_of(gadget_p, struct xxxxx_udc, gadget) #endif//__XXXXX_UDC_H__

.c

/***********************************
Copyright(C), 2013 LDP
FileName: xxxxx_udc.c
Author: wwxxxxll
Date:
Description: linux-3.2-36
History:
Author Date Desc
************************************/ #include <linux/module.h>//MODULE_*
#include <linux/init.h>//printk
#include <linux/slab.h>//kzalloc() kfree()
#include <linux/usb/gadget.h>//struct usb_gadget等
#include <linux/clk.h>//struct clk
#include <linux/platform_device.h>//platform
#include <linux/ioport.h>
#include <linux/interrupt.h>
#include <linux/delay.h> #include <asm/irq.h>
#include <asm/io.h>//ioremap #include "xxxxx_udc.h" #ifdef XXXXX_DEBUG_FS
#include <linux/debugfs.h>
#include <linux/seq_file.h>//seq_printf seq_read
#endif #define DRIVER_DESC "XXXXX USB Device Controller Gadget"
#define DRIVER_VERSION "2013"
#define DRIVER_AUTHOR "wwxxxxll" static const char gadget_name[] = "xxxxx_udc";
static const char driver_desc[] = DRIVER_DESC; //根据实际情况修改
static const char ep0name[] = "ep0";
static const char * const ep_name[] = {
ep0name,
"ep1",
}; #ifdef XXXXX_DEBUG_FS
static struct dentry *xxxxx_udc_debugfs_root; static int xxxxx_udc_debugfs_seq_show(struct seq_file *m, void *p)
{
seq_printf(m, "My name is %s\n", gadget_name); return 0;
} static int xxxxx_udc_debugfs_fops_open(struct inode *inode,
struct file *file)
{
return single_open(file, xxxxx_udc_debugfs_seq_show, NULL);
} static const struct file_operations xxxxx_udc_debugfs_fops =
{
.open = xxxxx_udc_debugfs_fops_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
.owner = THIS_MODULE,
};
#endif /***********************hardware_handler************************/
static void xxxxx_usb_reset(struct xxxxx_udc *dev)
{
//硬件操作
} //udc的这个中断,真是包罗万象,各硬件差别比较大
//简单一点说,就是清楚中断标致位,再根据中断标志位对应处理
//实际要复杂的多,如果是ep0,还会从fifo中取得usb_ctrlrequest
//进行对应的处理,我们在实现具体的实现时再说吧
static irqreturn_t xxxxx_udc_irq(int dummy, void *_dev)
{
return IRQ_HANDLED;
}
/***************************************************************/ /***********************queue***********************************/
//对于usb请求,一般都要维护一个list去管理请求 //端点list初始化,存入gadget里
static void xxxxx_usb_reinit(struct xxxxx_udc *dev)
{
u32 i; /* device/ep0 records init */
INIT_LIST_HEAD (&dev->gadget.ep_list);
dev->gadget.ep0 = &dev->ep[0].ep;//ep0单独存放
dev->gadget.speed = USB_SPEED_UNKNOWN;
dev->ep0state = EP0_IDLE;
INIT_LIST_HEAD (&dev->gadget.ep0->ep_list); for (i = 0; i < XXXXX_ENDPOINTS; i++) {
struct xxxxx_ep *ep = &dev->ep[i]; if (i != 0)
list_add_tail (&ep->ep.ep_list, &dev->gadget.ep_list); ep->dev = dev;
ep->desc = NULL;
ep->stopped = 0;
INIT_LIST_HEAD (&ep->queue);
}
} static void xxxxx_udc_done(struct xxxxx_ep *ep, struct xxxxx_request *req, int status)
{
struct xxxxx_udc *dev;
unsigned stopped = ep->stopped; list_del_init(&req->queue); if (likely (req->req.status == -EINPROGRESS))//正在进行中
req->req.status = status;
else
status = req->req.status; dev = ep->dev; /* don't modify queue heads during completion callback */
ep->stopped = 1;
//先解锁再加锁,加锁是在dequeue_all调用前做的
spin_unlock(&dev->lock);
req->req.complete(&ep->ep, &req->req);
spin_lock(&dev->lock);
ep->stopped = stopped;
} static void xxxxx_dequeue_all(struct xxxxx_ep *ep, int status)
{
struct xxxxx_request *req; if (&ep->queue == NULL)
return; while (!list_empty(&ep->queue)) //list_del_init会删除链表中的元素
{
req = list_entry(ep->queue.next, struct xxxxx_request, queue);
xxxxx_udc_done(ep, req, status);
}
} /***************************************************************/
//may not be the endpoint named "ep0".这是gadget.h的源话
/**************************ep_ops*******************************/
//描述端点操作 //当设备配置或接口设置改变时,驱动会enable或disable端口
static int xxxxx_udc_ep_enable(struct usb_ep *_ep, const struct usb_endpoint_descriptor *desc)
{
struct xxxxx_udc *dev;
struct xxxxx_ep *ep;
u32 max;
unsigned long flags; ep = to_xxxxx_ep(_ep);
if (!_ep || !desc || ep->desc
|| (desc->bDescriptorType != USB_DT_ENDPOINT)
|| (_ep->name == ep0name))
return -EINVAL;
dev = ep->dev;
if (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN)
return -ESHUTDOWN; max = usb_endpoint_maxp(desc) & 0x1fff; spin_lock_irqsave(&dev->lock, flags); _ep->maxpacket = max & 0x7fff;
ep->desc = desc; ep->stopped = 0;
#ifdef XXXXX_SETWEDHE
ep->wedged = 0;
#endif
ep->bEndpointAddress = desc->bEndpointAddress; //寄存器操作
spin_unlock_irqrestore(&dev->lock, flags); return 0;
} static int xxxxx_udc_ep_disable(struct usb_ep *_ep)
{
struct xxxxx_ep *ep = to_xxxxx_ep(_ep);
unsigned long flags; if (!_ep || !ep->desc) {
return -EINVAL;
} local_irq_save(flags); ep->desc = NULL;
ep->stopped = 1; //清除请求list和关闭ep
xxxxx_dequeue_all(ep, -ESHUTDOWN);//关机后将无法传输端点 local_irq_restore(flags); return 0;
} //动态分配请求
static struct usb_request *xxxxx_udc_alloc_request(struct usb_ep *_ep, gfp_t gfp_flags)
{
struct xxxxx_request *req; if (!_ep)
return NULL; req = kzalloc (sizeof(struct xxxxx_request), gfp_flags);
if (!req)
return NULL; INIT_LIST_HEAD (&req->queue); return &req->req;
} //释放请求
static void xxxxx_udc_free_request(struct usb_ep *_ep, struct usb_request *_req)
{
//struct xxxxx_ep *ep = to_xxxxx_ep(_ep);
struct xxxxx_request *req = to_xxxxx_req(_req); if (!_ep || !_req)
return; WARN_ON (!list_empty (&req->queue));
kfree(req);
} //下面的queue是插入一个请求
//dequeue删除一个请求
static int xxxxx_udc_queue(struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags)
{
struct xxxxx_udc *dev;
unsigned long flags;
struct xxxxx_request *req = to_xxxxx_req(_req);
struct xxxxx_ep *ep = to_xxxxx_ep(_ep); if (unlikely (!_ep || (!ep->desc && ep->num != 0))) //这个逻辑下面会看到很多(_ep为空或[ep->desc为空且不是0端点])
{
return -EINVAL;
} dev = ep->dev;
if (unlikely (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN))
{
return -ESHUTDOWN;
} if (unlikely(!_req || !_req->complete
|| !_req->buf || !list_empty(&req->queue))) //_req或_req->buf为空、complete执行错误、req->queue不为空
{
return -EINVAL;
} local_irq_save (flags); //硬件操作 if (likely(req != 0))
list_add_tail(&req->queue, &ep->queue);//请求入list local_irq_restore(flags); return 0;
} static int xxxxx_udc_dequeue(struct usb_ep *_ep, struct usb_request *_req)
{
struct xxxxx_ep *ep = to_xxxxx_ep(_ep);
struct xxxxx_udc *dev;
int retval = -EINVAL;
unsigned long flags;
struct xxxxx_request *req = NULL; if (!_ep || !_req)
return retval; dev = ep->dev; if (!dev->driver)
return -ESHUTDOWN; local_irq_save (flags); list_for_each_entry (req, &ep->queue, queue)
{
if (&req->req == _req)
{
list_del_init (&req->queue);
_req->status = -ECONNRESET;//Connection reset by peer
retval = 0;
break;
}
} if (retval == 0)
{
xxxxx_udc_done(ep, req, -ECONNRESET);
} local_irq_restore (flags);
return retval;
} #ifdef XXXXX_FIFO_STATUS
//fifo状态,返回fifo中的字节数。
//在上层的调用usb_ep_fifo_statu()如果不用fifo或不支持这个操作返回错误-EOPNOTSUPP
//net2272就有寄存器EP_AVAIL记录fifo中的字节数。
//s3c2440硬件不支持,没实现,上层调用会得到-EOPNOTSUPP
static int xxxxx_udc_fifo_status(struct usb_ep *_ep)
{
struct xxxxx_ep *ep;
u16 retval = 0; ep = to_xxxxx_ep(_ep);
if (!_ep || (!ep->desc && ep->num != 0))
return -ENODEV;
if (!ep->dev->driver || ep->dev->gadget.speed == USB_SPEED_UNKNOWN)
return -ESHUTDOWN; //retval = 读寄存器 return retval;
}
#endif #ifdef XXXXX_FIFO_FLUSH
//冲掉fifo的不明确数据,这个决不用除非端点不能用于任何协议传输,这是上层调用的事
static void xxxxx_udc_fifo_flush(struct usb_ep *_ep)
{
struct xxxxx_ep *ep; ep = to_xxxxx_ep(_ep);
if (!_ep || (!ep->desc && ep->num != 0))
return;
if (!ep->dev->driver || ep->dev->gadget.speed == USB_SPEED_UNKNOWN)
return; //寄存器操作
}
#endif /*
上层调用usb_ep_set_wedge
停止一个端点并忽略CLEAR_FEATURE请求。如果Gadget驱动清除停止状态,它将自动Unwedge端点
一般用一个位wedge表示
如果没有实现set_wedge方法。就用set_halt(ep, 1);代替
我们看个例子(在file_storage.c中)
Bulk-only
当出现无效的CBW时
Bulk-only Spec说我们必须停止IN 端点。还说必须保持这个状态知道下一次的reset,但是没有办法
告诉控制器忽略CLEAR_FEATURE请求。所以我们用一个位来记录,搞定!
下面是参考net2272的代码,
value=1:set_halt
= 0:clear_halt
*/
static int xxxxx_set_halt_and_wedge(struct usb_ep *_ep, int value, int wedged)
{
struct xxxxx_ep *ep;
unsigned long flags;
int ret = 0; ep = container_of(_ep, struct xxxxx_ep, ep);
if (!_ep || (!ep->desc && ep->num != 0))
return -EINVAL;
if (!ep->dev->driver || ep->dev->gadget.speed == USB_SPEED_UNKNOWN)
return -ESHUTDOWN;
if (ep->desc /* not ep0 */ && usb_endpoint_xfer_isoc(ep->desc))//判断是不是同步端点,见下面
return -EINVAL; spin_lock_irqsave(&ep->dev->lock, flags); if (!list_empty(&ep->queue))
ret = -EAGAIN;
#ifdef XXXXX_FIFO_STATUS
else if ((ep->bEndpointAddress & USB_DIR_IN) && value && xxxxx_udc_fifo_status(_ep) != 0)//fifo_status是上面实现的
ret = -EAGAIN;
#endif
else {
/* set/clear */
if (value) {
if (ep->num == 0)
{
ep->dev->ep0state = EP0_STALL;
ep->stopped = 1;
//net2272的端点0在setup时自动复位,没有什么操作。s3c2440就不是了
//ep->dev->protocol_stall = 1;
//ep0 set_halt
}
else
//epx(x != 0) set_halt
if (wedged)//维护wedged
ep->wedged = 1;
} else {
//ep clear_halt
ep->wedged = 0;
}
}
spin_unlock_irqrestore(&ep->dev->lock, flags); return ret;
}
//_ep 不能是同步端点,同步端点不支持错误重发机制。在上面判断
static int xxxxx_udc_set_halt(struct usb_ep *_ep, int value)
{
return xxxxx_set_halt_and_wedge(_ep, value, 0);
} #ifdef XXXXX_SETWEDHE static int xxxxx_udc_set_wedge(struct usb_ep *_ep)
{
if (!_ep || _ep->name == ep0name)//一般都是端点0请求复位
return -EINVAL; return xxxxx_set_halt_and_wedge(_ep, 1, 1);
}
#endif static const struct usb_ep_ops xxxxx_ep_ops =
{
.enable = xxxxx_udc_ep_enable,
.disable = xxxxx_udc_ep_disable, .alloc_request = xxxxx_udc_alloc_request,
.free_request = xxxxx_udc_free_request, .queue = xxxxx_udc_queue,
.dequeue = xxxxx_udc_dequeue, .set_halt = xxxxx_udc_set_halt, #ifdef XXXXX_SETWEDHE
.set_wedge = xxxxx_udc_set_wedge,
#endif #ifdef XXXXX_FIFO_STATUS
.fifo_status = xxxxx_udc_fifo_status,
#endif #ifdef XXXXX_FIFO_FLUSH
.fifo_flush = xxxxx_udc_fifo_flush,
#endif
}; /***************************************************************/
//USB 设备的常用操作包括:设备连接、设备移除、设备配置、地址分配、数据传输、
//设备挂起、设备唤醒等。
/**************************usb_gadget_ops***********************/
//硬件操作函数 //获取帧号,当主机发送USB 数据包时,每个帧的开始(SOF)包包含一个帧号。
//这个帧号一般自动加载到对应寄存器,此函数主要就是读这些寄存器
//如果设备不支持返回负
static int xxxxx_udc_get_frame(struct usb_gadget *usb_gdt_p)
{
#ifdef XXXXX_GET_FRAME
struct xxxxx_udc *dev = to_xxxxx_udc(usb_gdt_p);
int retval = 0;
unsigned long flags; spin_lock_irqsave(&dev->lock, flags); //retval = 读寄存器 spin_unlock_irqrestore(&dev->lock, flags); return retval;
#else
return -EOPNOTSUPP;
#endif
} #ifdef XXXXX_WAKEUP
//唤醒,举个例子net2272。它的寄存器usbctl0的第五位控制唤醒功能使能
//寄存器usbctl1的第三位通过写1去resume,s3c2440在PWR_REG也有类似
static int xxxxx_udc_wakeup(struct usb_gadget *usb_gdt_p)
{
struct xxxxx_udc *dev = to_xxxxx_udc(usb_gdt_p);
unsigned long flags; spin_lock_irqsave(&dev->lock, flags); //寄存器操作 spin_unlock_irqrestore(&dev->lock, flags); return 0;
}
#endif #ifdef XXXXX_SELFPOWERED
//设置自供电标志(selfpowered feature),一般就用一个变量位或一个位记录一下。USB_RECIP_DEVICE时返回状态
static int xxxxx_udc_set_selfpowered (struct usb_gadget *usb_gdt_p, int is_selfpowered)
{
struct xxxxx_udc *dev = to_xxxxx_udc(usb_gdt_p); if (is_selfpowered)
dev->devstatus |= (1 << USB_DEVICE_SELF_POWERED);
else
dev->devstatus &= ~(1 << USB_DEVICE_SELF_POWERED); return 0;
}
#endif #ifdef XXXXX_VBUS_SESSION
//vbus在硬件上就是usb的电源脚,这个函数就是来控制它。一般通过一个gpio拉高拉底
//这个vbus会话,实际的我看了s3c2410和at91的处理,就是让usb的D+线与一个gpio口连接
//通过置1置0来控制usb
static int xxxxx_udc_vbus_session (struct usb_gadget *usb_gdt_p, int is_active)
{
struct xxxxx_udc *dev = to_xxxxx_udc(usb_gdt_p);
unsigned long flags; spin_lock_irqsave(&dev->lock, flags); //寄存器操作 spin_unlock_irqrestore(&dev->lock, flags); return 0;
}
#endif #ifdef XXXXX_VBBUS_DRAW
//强制vbus电源控制器行为,在SET_CONFIGRATION时,设置vbus的电流量
//vbus应该是表示总线电压,在硬件上是一个脚
//主要是对usb电流的设置,看一下gta02平台,这个函数会操作pcf50633(一种移动设备的电源管理芯片)
static int xxxxx_udc_vbus_draw (struct usb_gadget *usb_gdt_p, unsigned mA)
{
return 0;
}
#endif #ifdef XXXXXX_PULLUP
//这个和上面的vbus_session区别是
//vbus_session是控制vbus的连接
//pullup是控制usb模块的连接
//在udc-core.c中newstyle的驱动probe函数时才调用它,所以你要实现udc_start和udc_stop,
//当然除了注册,也可以通过sysfs调用它。和newstyle无关。
//composite.c也有一些调用
//这个就是根据is_on来connect或disconnect usb
//net2272就是由USBCTL0的第三位控制的,s3c2440还是通过gpio和vbus_session没
//区别
static int xxxxx_udc_pullup (struct usb_gadget *usb_gdt_p, int is_on)
{
struct xxxxx_udc *dev = to_xxxxx_udc(usb_gdt_p);
unsigned long flags; spin_lock_irqsave(&dev->lock, flags); if (is_on)
{
//enable
}
else
{
//disable
} spin_unlock_irqrestore(&dev->lock, flags); return 0;
}
#endif //不好意思,我看了linux-3.2.36的/gadget的目录没发现有实现这个的硬件
static int xxxxx_udc_ioctl(struct usb_gadget *usb_gdt_p, unsigned code, unsigned long param)
{
return 0;
} //这个也没看驱动实现它,从名字就是获取配置参数,就简单看看struct usb_dcd_config_params
/*
struct usb_dcd_config_params {
__u8 bU1devExitLat; // U1 Device exit Latency u1设备等待时间
#define USB_DEFAULT_U1_DEV_EXIT_LAT 0x01 // Less then 1 microsec 至少1微秒
__le16 bU2DevExitLat; // U2 Device exit Latency
#define USB_DEFAULT_U2_DEV_EXIT_LAT 0x1F4 // Less then 500 microsec
};
对应struct usb_ss_cap_descriptor 中的成员
每一个I/O请求包延迟时间限制
*/
static void xxxxx_udc_get_config_params(struct usb_dcd_config_params *usb_dc_cfg_pm)
{
} //在udc-core.c中start和udc_start的解释一样,在bind()之前调用,只要实现一个就行了
//我知道start主要有bind回调
//udc_start主要是设备执行了non-control请求后,要重新连接,net2272和r8a66597实现的就是它
#ifdef XXXXX_NEWSTYLE
static int xxxxx_udc_start(struct usb_gadget *usb_gdt_p, struct usb_gadget_driver *driver);
static int xxxxx_udc_stop(struct usb_gadget *usb_gdt_p, struct usb_gadget_driver *driver);
#else
//s3c2410 xxxxx 实现它
static int xxxxx_start(struct usb_gadget_driver *driver, int (*bind)(struct usb_gadget *));
static int xxxxx_stop(struct usb_gadget_driver *driver);
#endif static const struct usb_gadget_ops xxxxx_ops =
{
.get_frame = xxxxx_udc_get_frame,
#ifdef XXXXX_WAKEUP
.wakeup = xxxxx_udc_wakeup,
#endif #ifdef XXXXX_SELFPOWERED
.set_selfpowered = xxxxx_udc_set_selfpowered,
#endif #ifdef XXXXX_VBUS_SESSION
.vbus_session = xxxxx_udc_vbus_session,
#endif #ifdef XXXXX_VBBUS_DRAW
.vbus_draw = xxxxx_udc_vbus_draw,
#endif #ifdef XXXXXX_PULLUP
.pullup = xxxxx_udc_pullup,
#endif .ioctl = xxxxx_udc_ioctl,
.get_config_params = xxxxx_udc_get_config_params,
#ifdef XXXXX_NEWSTYLE
.udc_start = xxxxx_udc_start,
.udc_stop = xxxxx_udc_stop,
#else
.start = xxxxx_start,
.stop = xxxxx_stop,
#endif
}; /***************************************************************/ /***************************************************************/ static struct xxxxx_udc udc_info = {
.gadget = {
.ops = &xxxxx_ops,
.ep0 = &udc_info.ep[0].ep,
.name = gadget_name,
.dev = {
.init_name = "gadget",
},
/*
根据自己的硬件选择
unsigned is_dualspeed:1;
unsigned is_otg:1;
unsigned is_a_peripheral:1;
unsigned b_hnp_enable:1; //hnp:主机协商协议 otg特有的
unsigned a_hnp_support:1;
unsigned a_alt_hnp_support:1;
*/
}, /* control endpoint */
.ep[0] = {
.num = 0,
.ep =
{
.name = "ep0",
.ops = &xxxxx_ep_ops,
.maxpacket = EP0_FIFO_SIZE,
},
.dev = &udc_info,
}, /* first group of endpoints */
.ep[1] = {
.num = 1,
.ep =
{
.name = "ep1",
.ops = &xxxxx_ep_ops,
.maxpacket = EP1_FIFO_SIZE,
},
.dev = &udc_info,
.fifo_size = EP1_FIFO_SIZE,
.bEndpointAddress = EP1_ADDRESS,
.bmAttributes = EP1_ATTR,
},
}; static void stop_activity(struct xxxxx_udc *dev, struct usb_gadget_driver *driver)
{
unsigned i; if (dev->gadget.speed == USB_SPEED_UNKNOWN)
driver = NULL; /* disconnect gadget driver after quiesceing hw and the driver */ xxxxx_usb_reset(dev);//复位或disable
for (i = 0; i < XXXXX_ENDPOINTS; i++)
{
xxxxx_dequeue_all(&dev->ep[i], -ECONNABORTED);
} #ifndef XXXXX_NEWSTYLE
/*
if (udc_is_newstyle(udc)) {
udc->driver->disconnect(udc->gadget);
udc->driver->unbind(udc->gadget);
usb_gadget_udc_stop(udc->gadget, udc->driver);
usb_gadget_disconnect(udc->gadget);//对应pull_up
} else {
usb_gadget_stop(udc->gadget, udc->driver);//所以非newstyle要disconnect
}
*/
if (driver)
{
spin_unlock(&dev->lock);
driver->disconnect(&dev->gadget);
spin_lock(&dev->lock);
}
#endif if (dev->driver)
{
xxxxx_usb_reinit(dev);//重初始化
}
} #ifdef XXXXX_NEWSTYLE
/*
udc 的probe函数
if (udc_is_newstyle(udc)) {//是否实现udc_start and udc_stop
ret = bind(udc->gadget);
if (ret)
goto err1;
ret = usb_gadget_udc_start(udc->gadget, driver);//已绑定,bind是gadget实现的
if (ret) {
driver->unbind(udc->gadget);
goto err1;
}
usb_gadget_connect(udc->gadget);//上面的pullup
} else { ret = usb_gadget_start(udc->gadget, driver, bind);
if (ret)
goto err1; }
*/
//net2272和r8a66597实现的就是它
//下面参考net2272
static int xxxxx_udc_start(struct usb_gadget *usb_gdt_p, struct usb_gadget_driver *driver)
{
struct xxxxx_udc *dev; if (!driver || !driver->unbind || !driver->setup ||
driver->speed != USB_SPEED_HIGH)
return -EINVAL; dev = container_of(usb_gdt_p, struct xxxxx_udc, gadget); /* hook up the driver ... */
driver->driver.bus = NULL;
dev->driver = driver;
dev->gadget.dev.driver = &driver->driver; //使能udc,硬件操作 return 0;
} static int xxxxx_udc_stop(struct usb_gadget *usb_gdt_p, struct usb_gadget_driver *driver)
{
struct xxxxx_udc *dev;
unsigned long flags; dev = container_of(usb_gdt_p, struct xxxxx_udc, gadget); spin_lock_irqsave(&dev->lock, flags);
stop_activity(dev, driver);
spin_unlock_irqrestore(&dev->lock, flags); dev->gadget.dev.driver = NULL;
dev->driver = NULL; return 0;
} #else
//s3c2410 goku实现它,参考goku
static int xxxxx_start(struct usb_gadget_driver *driver, int (*bind)(struct usb_gadget *usb_gdt_p))
{
struct xxxxx_udc *dev = &udc_info;
int retval = 0; if (!driver
|| driver->speed < USB_SPEED_FULL
|| !bind
|| !driver->disconnect
|| !driver->setup)
return -EINVAL;
if (!dev)
return -ENODEV;
if (dev->driver)
return -EBUSY; /* hook up the driver */
driver->driver.bus = NULL;
dev->driver = driver;
dev->gadget.dev.driver = &driver->driver; if ((retval = device_add(&dev->gadget.dev)) != 0)
{
goto register_error;
} retval = bind(&dev->gadget);
if (retval)
{
device_del(&dev->gadget.dev);
goto register_error;
} //使能udc,硬件操作 register_error:
dev->driver = NULL;
dev->gadget.dev.driver = NULL;
return retval;
} static int xxxxx_stop(struct usb_gadget_driver *driver)
{
struct xxxxx_udc *dev = &udc_info;
unsigned long flags; if (!dev)
return -ENODEV;
if (!driver || driver != dev->driver || !driver->unbind)
return -EINVAL; spin_lock_irqsave(&dev->lock, flags);
dev->driver = NULL;
stop_activity(dev, driver);
spin_unlock_irqrestore(&dev->lock, flags); driver->unbind(&dev->gadget);
dev->gadget.dev.driver = NULL;
dev->driver = NULL; device_del(&dev->gadget.dev); return 0;
}
#endif /***************************************************************/
static int xxxxx_udc_probe(struct platform_device *pdev)
{ struct xxxxx_udc *udc = &udc_info;
struct device *dev = &pdev->dev;
int retval;
struct resource *res;
#ifdef XXXXX_USE_IRQ
struct resource *resirq;
#endif
resource_size_t res_size; dev_dbg(dev, "%s()\n", __func__); #ifdef XXXXX_HAVE_CLK
udc->xxxxx_clk = clk_get(NULL, "xxxxx");
if (IS_ERR(udc->xxxxx_clk))
{
dev_err(dev, "failed to get usb bus clock source\n");
return PTR_ERR(udc->xxxxx_clk);
} clk_enable(udc->xxxxx_clk); #if (CLK_DELAY_TIME != 0)
mdelay(CLK_DELAY_TIME);
#endif dev_dbg(dev, "got and enabled clocks\n");
#endif //XXXXX_HAVE_CLK if (strncmp(pdev->name, "xxxxx", 7) == 0) {
dev_info(dev, "xxxxx: increasing FIFO to %d bytes\n", XXXXX_EP_FILO_SIZE);
udc_info.ep[1].fifo_size = XXXXX_EP_FILO_SIZE;
} spin_lock_init (&udc->lock); res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res)
{
dev_err(&pdev->dev, "can't get device resources\n");
retval = -ENODEV;
goto err_clk;
}
/*
pdata = pdev->dev.platform_data;
if (!pdata) {
dev_err(&pdev->dev, "driver needs platform data\n");
return -ENODEV;
}
*/
res_size = resource_size(res);
if (!request_mem_region(res->start, res_size, res->name))
{
dev_err(&pdev->dev, "can't allocate %d bytes at %d address\n",
res_size, res->start);
retval = -ENOMEM; goto err_clk;
} udc->virl_addr = ioremap(res->start, res_size);
if (!udc->virl_addr)
{
retval = -ENOMEM;
goto err_mem;
}
udc->phy_addr = res->start;
udc->reg_size = res_size; device_initialize(&udc->gadget.dev);
udc->gadget.dev.parent = &pdev->dev;
udc->gadget.dev.dma_mask = pdev->dev.dma_mask; platform_set_drvdata(pdev, udc); //少不了硬件初始化
xxxxx_usb_reset(udc);
xxxxx_usb_reinit(udc); #ifdef XXXXX_USE_IRQ
resirq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (!resirq)
{
dev_err(&pdev->dev, "can't get device irq resources\n");
retval = -ENODEV;
goto err_map;
} udc->irq_num = resirq->start; /* irq setup after old hardware state is cleaned up */
retval = request_irq(udc->irq_num, xxxxx_udc_irq, 0, gadget_name, udc);
if (retval != 0)
{
dev_err(dev, "cannot get irq %i, err %d\n", udc->irq_num, retval);
retval = -EBUSY;
goto err_map;
} dev_dbg(dev, "got irq %i\n", udc->irq_num); #endif retval = usb_add_gadget_udc(&pdev->dev, &udc->gadget);
if (retval)
goto err_int; #ifdef XXXXX_DEBUG_FS
if (xxxxx_udc_debugfs_root)
{
udc->debug_info = debugfs_create_file("registers", S_IRUGO, xxxxx_udc_debugfs_root,
udc, &xxxxx_udc_debugfs_fops);
if (!udc->debug_info)
dev_warn(dev, "debugfs file creation failed\n");
}
#endif dev_dbg(dev, "probe ok\n"); return 0; err_int:
#ifdef XXXXX_USE_IRQ
free_irq(udc->irq_num, udc);
#endif
err_map:
iounmap(udc->virl_addr);
err_mem:
release_mem_region(res->start, res_size);
err_clk:
#ifdef XXXXX_HAVE_CLK
clk_put(udc->xxxxx_clk);
clk_disable(udc->xxxxx_clk);
#endif return retval;
} static int xxxxx_udc_remove(struct platform_device *pdev)
{
struct xxxxx_udc *udc = platform_get_drvdata(pdev); dev_dbg(&pdev->dev, "%s()\n", __func__); usb_del_gadget_udc(&udc->gadget);
if (udc->driver)
return -EBUSY; #ifdef XXXXX_DEBUG_FS
debugfs_remove(udc->debug_info);
#endif #ifdef XXXXX_USE_IRQ
free_irq(udc->irq_num, udc);
#endif iounmap(udc->virl_addr);
release_mem_region(udc->phy_addr, udc->reg_size); platform_set_drvdata(pdev, NULL); #ifdef XXXXX_HAVE_CLK
if (!IS_ERR(udc->xxxxx_clk) && udc->xxxxx_clk != NULL) {
clk_disable(udc->xxxxx_clk);
clk_put(udc->xxxxx_clk);
udc->xxxxx_clk = NULL;
}
#endif dev_dbg(&pdev->dev, "%s: remove ok\n", __func__); return 0;
} #ifdef CONFIG_PM
static int xxxxx_udc_suspend(struct platform_device *pdev, pm_message_t message)
{
return 0;
} static int xxxxx_udc_resume(struct platform_device *pdev)
{
return 0;
}
#else
#define xxxxx_udc_suspend NULL
#define xxxxx_udc_resume NULL
#endif /***************************************************************/ static const struct platform_device_id xxxxx_udc_ids[] = {
{ "xxxxx-usbgadget", },
{ }
}; MODULE_DEVICE_TABLE(platform, xxxxx_udc_ids); //有些设备可能用struct pci_driver,我就不考虑这么多了。
static struct platform_driver udc_driver_xxxxx = {
.driver = {
.name = "xxxxx-usbgadget",
.owner = THIS_MODULE,
},
.probe = xxxxx_udc_probe,
.remove = __exit_p(xxxxx_udc_remove),
.suspend = xxxxx_udc_suspend,
.resume = xxxxx_udc_resume,
.id_table = xxxxx_udc_ids,
}; static int __init udc_init(void)
{
int retval; xxxxx_udc_debugfs_root = debugfs_create_dir(gadget_name, NULL);
if (IS_ERR(xxxxx_udc_debugfs_root)) {
printk(KERN_ERR "%s: debugfs dir creation failed %ld\n",
gadget_name, PTR_ERR(xxxxx_udc_debugfs_root));
xxxxx_udc_debugfs_root = NULL;
} retval = platform_driver_register(&udc_driver_xxxxx);
if (retval)
goto err; return 0; err:
debugfs_remove(xxxxx_udc_debugfs_root);
return retval;
} static void __exit udc_exit(void)
{
platform_driver_unregister(&udc_driver_xxxxx);
debugfs_remove(xxxxx_udc_debugfs_root);
} module_init(udc_init);
module_exit(udc_exit); MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_VERSION(DRIVER_VERSION);
MODULE_LICENSE("GPL");

下期预告:

基于模板实现一个实际的udc驱动。

[置顶] 自娱自乐1之Linux UDC驱动(形式模板)的更多相关文章

  1. [置顶] 自娱自乐7之Linux UDC驱动2(自编udc驱动,现完成枚举过程,从驱动代码分析枚举过程)

    花了半个月,才搞定驱动中的枚举部分,现在说linux的枚举,windows可能有差别. 代码我会贴在后面,现在只是实现枚举,你可能对代码不感兴趣,我就不分析代码了,你可以看看 在<自娱自乐1&g ...

  2. [置顶] 自娱自乐6之Linux gadget驱动5(自编gadget驱动,包涵与之通讯的主机usb驱动,已调试通过)

    这个代码调试,你首先要保证你的udc驱动没用问题,这个有些矛盾,应为我本来要用gadget驱动来调试udc驱动,结果反过来了. 这是在zero基础改的,大概的改动 1. 去掉loop. 2. sink ...

  3. [置顶] 学习鸟哥的Linux私房菜笔记(6)——过滤器、输入输出及管道

    一.过滤器 Linux中的应用工具分为三种: 交互工具 过滤器 编辑器 能够接受数据,过滤再输出的工具,称之为过滤器 对过滤器和进程,存在着输入源与输出对象 二.输入.输出.重定向 输入:过滤器的数据 ...

  4. [置顶] getopt_long函数基本用法-linux

    一.感性认识: [c-sharp]  view plain copy   #include <stdio.h> #include <getopt.h> char * l_opt ...

  5. android驱动[置顶] 我的DIY Android之旅--驱动并控制你的Android开发板蜂鸣器

    改章节个人在深圳游玩的时候突然想到的...这几周就有想写几篇关于android驱动的博客,所以回家到之后就奋笔疾书的写出来发布了 这些天一直在想Android驱动框架层的实现,本文借助老罗教师的博客和 ...

  6. ahk之路:利用ahk在window7下实现窗口置顶

    操作系统:win7 64位 ahk版本:autohotkey_L1.1.24.03 今天安装了AutoHotkey_1.1.24.03.SciTE.PuloversMacroCreator,重新开始我 ...

  7. [自娱自乐] 3、超声波测距模块DIY笔记(三)

    前言 上一节我们已经研究了超声波接收模块并自己设计了一个超声波接收模块,在此基础上又尝试用单片机加反相器构成生成40KHz的超声波发射电路,可是发现采用这种设计的发射电路存在严重的发射功率太低问题,对 ...

  8. 在UWP中页面滑动导航栏置顶

    最近在研究掌上英雄联盟,主要是用来给自己看新闻,顺便copy个界面改一下段位装装逼,可是在我copy的时候发现这个东西 当你滑动到一定距离的时候导航栏会置顶不动,这个特性在微博和淘宝都有,我看了@ms ...

  9. WinFrom窗体始终置顶

    调用WindowsAPI使窗体始终保持置顶效果,不被其他窗体遮盖: [DllImport("user32.dll", CharSet = CharSet.Auto)] privat ...

随机推荐

  1. flutter android沉浸式状态栏

    import 'package:flutter/services.dart'; import 'dart:io'; class _MyAppState extends State<MyApp&g ...

  2. Python之路【第三篇】:文件操作

    一.文件操作步骤 打开文件,得到文件句柄并赋值给一个变量 通过句柄对文件进行操作 关闭文件 歌名:<大火> 演唱:李佳薇 作词:姚若龙 作曲:马奕强 歌词: 有座巨大的停了的时钟 倾倒在赶 ...

  3. JSP内置对象——application对象和out对象

    1.application 对象application用于保存所有应用程序的公有数据.它在服务器启动时自动创建,在服务器停止时销毁. 当application对象没有被销毁时,所有用户都可以共享该ap ...

  4. Arduino可穿戴教程之第一个程序——上传运行程序(四)

    Arduino可穿戴教程之第一个程序——上传运行程序(四) 2.4.5  上传程序 现在所有Arduino IDE的设置都完成了,我们就可以将示例程序上传到板子中了.这非常简单,只需要单击如图2.45 ...

  5. 【BZOJ 3566】 3566: [SHOI2014]概率充电器 (概率树形DP)

    3566: [SHOI2014]概率充电器 Description 著名的电子产品品牌 SHOI 刚刚发布了引领世界潮流的下一代电子产品——概率充电器:“采用全新纳米级加工技术,实现元件与导线能否通电 ...

  6. BZOJ2303 APIO2011方格染色

    这题太神了 首先我们可以发现只有当i和j都是偶数时a[1][1]^a[1][j]^a[i][1]^a[i][j]=1才满足情况,其它时都为0 所以我们可以先把i和j都为偶数的地方^1变为0 下面才是最 ...

  7. GeoIP的使用

    GeoIP介绍: 什么是GepIP ? 所谓GeoIP,就是通过来访者的IP, 定位他的经纬度,国家/地区,省市,甚至街道等位置信息.这里面的技术不算难题,关键在于有个精准 的数据库.有了准确的数据源 ...

  8. Java高级架构师(一)第42节:应用上Nginx过后的体系结构

    以后的架构思考方向: 体系结构的演变

  9. WampServer -- “You don't have permission to access /phpmyadmin/ on this server.”

    当你安装完成wamp后,打开localhost或ip时发现已经可以运行了 但想使用phpmyadmin时,发现提示如下内容: You don't have permission to access / ...

  10. GIT 提交步骤

    1.提交 git add .