一、驱动总体概述
本次的驱动代码是Samsung公司为s5pv210这款SoC编写的framebuffer驱动,对应于s5pv210中的内部外设Display Controller (FIMD)模块。
驱动代码是基于platform平台总线编写的。
1、驱动代码的源文件分布:
(1):drivers/video/samsung/s3cfb.c, 驱动主体
(2):drivers/video/samsung/s3cfb_fimd6x.c,里面有很多LCD硬件操作的函数
(3):arch/arm/mach-s5pv210/mach-x210.c,负责提供platform_device,这个文件里面提供了很多的基于platform总线编写的驱动需要的platform_device。
mach文件是每一个移植好的内核都会提供这个文件的,例如这里的mach-x210.c文件是开发板厂商从三星提供的mach文件移植而来的。
(4):arch/arm/plat-s5p/devs.c,为platform_device提供一些硬件描述信息
2、当我们接触到一种新的驱动框架的时候,怎么能够找到驱动框架源代码(入口函数)所在哪个源文件中?
(1):经验:靠经验的前提是你之前就已经接触过很多的驱动框架,你能够靠你的经验大概猜出来是哪些文件
(2):可以分析内核源码树中menuconfig、Makefile、Kconfig等
(3):内核编译后检查编译结果中的.o文件
二、platform_driver平台设备驱动部分
1、注册/卸载平台驱动:s3cfb_register/s3cfb_unregister (drivers\video\samsung\s3cfb.c)
(1)platform_driver结构体变量s3cfb_driver
static struct platform_driver s3cfb_driver = {
.probe = s3cfb_probe, // 平台的probe函数
.remove = __devexit_p(s3cfb_remove),
.driver = {
.name = S3CFB_NAME, // 平台设备驱动的名字 s3cfb
.owner = THIS_MODULE,
},
};
2、相关的数据结构
struct s3c_platform_fb {
int hw_ver;
char clk_name[];
int nr_wins; // 这个表示虚拟窗口的数量
int nr_buffers[];
int default_win; // 这个表示当前默认的窗口
int swap;
phys_addr_t pmem_start; /* starting physical address of memory region */ // 显存的物理起始地址
size_t pmem_size; /* size of memory region */ // 显存的字节大小
void *lcd;
void (*cfg_gpio)(struct platform_device *dev); // LCD相关gpio的配置
int (*backlight_on)(struct platform_device *dev); // 打开LCD的背光
int (*backlight_onoff)(struct platform_device *dev, int onoff); // 关闭LCD的背光
int (*reset_lcd)(struct platform_device *dev); // 复位LCD
int (*clk_on)(struct platform_device *pdev, struct clk **s3cfb_clk); // LCD相关的时钟打开
int (*clk_off)(struct platform_device *pdev, struct clk **clk); // LCD相关的时钟关闭
};
struct s3cfb_global {
/* general */
void __iomem *regs; // SoC中LCD控制器部分相关的寄存器地址的基地址(虚拟地址) Display Controller (FIMD)模块
struct mutex lock; // 互斥锁
struct device *dev; // 表示本fb设备的device指针
struct clk *clock;
struct regulator *regulator;
int irq; // 本LCD使用到的中断号
struct fb_info **fb; // fb_info 的二重指针 用来指向一个 fb_info 指针数组
struct completion fb_complete; /* fimd */
int enabled;
int dsi;
int interlace;
enum s3cfb_output_t output; // LCD的输出模式
enum s3cfb_rgb_mode_t rgb_mode; // RGB色彩模式
struct s3cfb_lcd *lcd; // 用来描述一个LCD的硬件信息 #ifdef CONFIG_HAS_WAKELOCK
struct early_suspend early_suspend;
struct wake_lock idle_lock;
#endif #ifdef CONFIG_CPU_FREQ
struct notifier_block freq_transition;
struct notifier_block freq_policy;
#endif };
struct s3cfb_lcd {
int width; // 水平像素
int height; // 垂直像素
int p_width; // 物理宽度 mm
int p_height; // 物理高度mm
int bpp; // 像素深度
int freq; // LCD的刷新率
struct s3cfb_lcd_timing timing; // LCD时序相关的参数
struct s3cfb_lcd_polarity polarity; // 这个是用来表示LCD的各种电平信号是否需要进行翻转 void (*init_ldi)(void); // 用来初始化 LDI 我不知道LDI是什么东西
void (*deinit_ldi)(void);
};
3、函数详解
(1)s3cfb_probe函数分析:
static int __devinit s3cfb_probe(struct platform_device *pdev)
{
struct s3c_platform_fb *pdata; // 这个是三星封装的一个用来表示平台设备层的私有数据的结构体
struct s3cfb_global *fbdev; // 设备驱动部分封装的一个全局的结构体,这个结构体主要作用是在驱动部分的2个文件(s3cfb.c和s3cfb_fimd6x.c)的函数中做数据传递用的
struct resource *res; // 定义一个资源结构体指针
int i, j, ret = ; fbdev = kzalloc(sizeof(struct s3cfb_global), GFP_KERNEL); // 给 fpdev 申请分配内存
if (!fbdev) {
dev_err(&pdev->dev, "failed to allocate for "
"global fb structure\n");
ret = -ENOMEM;
goto err_global;
}
fbdev->dev = &pdev->dev; // 通过 fbdev->dev 指向 pdev->dev /sys/devices/platform/s3cfb/ 这个目录作为fb设备的父设备目录 fbdev->regulator = regulator_get(&pdev->dev, "pd"); // 调整器 : 动态电流和电压控制,具体的我也不清楚
if (!fbdev->regulator) {
dev_err(fbdev->dev, "failed to get regulator\n");
ret = -EINVAL;
goto err_regulator;
}
ret = regulator_enable(fbdev->regulator);
if (ret < ) {
dev_err(fbdev->dev, "failed to enable regulator\n");
ret = -EINVAL;
goto err_regulator;
}
pdata = to_fb_plat(&pdev->dev); // 获取平台设备层的私有数据 pdev->dev-> platform_data 存放在 pdata中
if (!pdata) {
dev_err(fbdev->dev, "failed to get platform data\n");
ret = -EINVAL;
goto err_pdata;
} fbdev->lcd = (struct s3cfb_lcd *)pdata->lcd; // 通过fbdev->lcd 指向 pdata->lcd if (pdata->cfg_gpio) // 如果平台设备的私有数据中的cfg_gpio指向了一个有效的配置LCD相关的gpio的方法
pdata->cfg_gpio(pdev); // 则调用这个函数 if (pdata->clk_on) // 打开LCD相关的时钟设置
pdata->clk_on(pdev, &fbdev->clock); res = platform_get_resource(pdev, IORESOURCE_MEM, ); // 获取平台设备的IO资源
if (!res) {
dev_err(fbdev->dev, "failed to get io memory region\n");
ret = -EINVAL;
goto err_io;
} res = request_mem_region(res->start, // 请求进行物理地址到虚拟地址的映射
res->end - res->start + , pdev->name);
if (!res) {
dev_err(fbdev->dev, "failed to request io memory region\n");
ret = -EINVAL;
goto err_io;
} fbdev->regs = ioremap(res->start, res->end - res->start + ); // 申请物理地址到虚拟地址的映射,将映射得到的虚拟地址存放在 fbdev->regs
if (!fbdev->regs) {
dev_err(fbdev->dev, "failed to remap io region\n");
ret = -EINVAL;
goto err_mem;
} s3cfb_set_vsync_interrupt(fbdev, ); // 使能vsync中断(场同步信号中断)
s3cfb_set_global_interrupt(fbdev, ); // 全局中断使能: 使能视频帧中断 和 使能视频中断
s3cfb_init_global(fbdev); // 全局初始化 if (s3cfb_alloc_framebuffer(fbdev)) { // 给fb_info 申请分配内存 并构建fb_info结构体
ret = -ENOMEM;
goto err_alloc;
} if (s3cfb_register_framebuffer(fbdev)) { // 注册fb设备 内部其实就是调用了FB驱动框架层中 register_framebuffer 函数进行注册
ret = -EINVAL;
goto err_register;
} s3cfb_set_clock(fbdev); // 时钟设置
s3cfb_set_window(fbdev, pdata->default_win, ); // 虚拟窗口相关的设置 s3cfb_display_on(fbdev); // 打开LCD显示 fbdev->irq = platform_get_irq(pdev, ); // 获取平台设备私有数据中的 中断号资源
if (request_irq(fbdev->irq, s3cfb_irq_frame, IRQF_SHARED, // 申请中断
pdev->name, fbdev)) {
dev_err(fbdev->dev, "request_irq failed\n");
ret = -EINVAL;
goto err_irq;
} #ifdef CONFIG_FB_S3C_LCD_INIT
if (pdata->backlight_on)
pdata->backlight_on(pdev); if (!bootloaderfb && pdata->reset_lcd)
pdata->reset_lcd(pdev);
#endif #ifdef CONFIG_HAS_EARLYSUSPEND
fbdev->early_suspend.suspend = s3cfb_early_suspend;
fbdev->early_suspend.resume = s3cfb_late_resume;
fbdev->early_suspend.level = EARLY_SUSPEND_LEVEL_DISABLE_FB;
register_early_suspend(&fbdev->early_suspend);
#endif ret = device_create_file(&(pdev->dev), &dev_attr_win_power); // 在平台设备下 /sys/devices/platform/pdev_dev/dev_attr_win_power 属性文件
if (ret < ) // pdev_dev表示的就是我们的平台设备的名字
dev_err(fbdev->dev, "failed to add sysfs entries\n"); dev_info(fbdev->dev, "registered successfully\n"); #if !defined(CONFIG_FRAMEBUFFER_CONSOLE) && defined(CONFIG_LOGO) // 下面这个是处理Linux启动logo 相关的代码
if (fb_prepare_logo( fbdev->fb[pdata->default_win], FB_ROTATE_UR)) {
printk("Start display and show logo\n");
/* Start display and show logo on boot */
fb_set_cmap(&fbdev->fb[pdata->default_win]->cmap, fbdev->fb[pdata->default_win]);
fb_show_logo(fbdev->fb[pdata->default_win], FB_ROTATE_UR);
}
#endif
mdelay();
if (pdata->backlight_on) // 打开背光
pdata->backlight_on(pdev); return ; err_irq:
s3cfb_display_off(fbdev);
s3cfb_set_window(fbdev, pdata->default_win, );
for (i = pdata->default_win;
i < pdata->nr_wins + pdata->default_win; i++) {
j = i % pdata->nr_wins;
unregister_framebuffer(fbdev->fb[j]);
}
err_register:
for (i = ; i < pdata->nr_wins; i++) {
if (i == pdata->default_win)
s3cfb_unmap_default_video_memory(fbdev->fb[i]);
framebuffer_release(fbdev->fb[i]);
}
kfree(fbdev->fb); err_alloc:
iounmap(fbdev->regs); err_mem:
release_mem_region(res->start,
res->end - res->start + ); err_io:
pdata->clk_off(pdev, &fbdev->clock); err_pdata:
regulator_disable(fbdev->regulator); err_regulator:
kfree(fbdev); err_global:
return ret;
}
(2)s3cfb_init_global
static int s3cfb_init_global(struct s3cfb_global *ctrl)
{
ctrl->output = OUTPUT_RGB; // 设置初始模式
ctrl->rgb_mode = MODE_RGB_P; // 设置RGB色彩模式 init_completion(&ctrl->fb_complete); // 初始化完成量(注: 完成量也是一种内核提供的同步机制)
mutex_init(&ctrl->lock); s3cfb_set_output(ctrl); // 寄存器配置LCD的输出模式
s3cfb_set_display_mode(ctrl); // 寄存器配置LCD的显示模式
s3cfb_set_polarity(ctrl); // 寄存器配置信号电平翻转
s3cfb_set_timing(ctrl); // 寄存器配置LCD时序参数
s3cfb_set_lcd_size(ctrl); // 寄存器配置LCD的水平、垂直像素大小 return ;
}
(3)s3cfb_alloc_framebuffer
static int s3cfb_alloc_framebuffer(struct s3cfb_global *ctrl)
{
struct s3c_platform_fb *pdata = to_fb_plat(ctrl->dev); // 通过 ctrl->dev 去获取平台设备的私有数据
int ret, i; ctrl->fb = kmalloc(pdata->nr_wins * // 给ctrl->fb 的这个fb_info指针数组分配内存
sizeof(*(ctrl->fb)), GFP_KERNEL); // 数量 nr_wins
if (!ctrl->fb) {
dev_err(ctrl->dev, "not enough memory\n");
ret = -ENOMEM;
goto err_alloc;
} for (i = ; i < pdata->nr_wins; i++) { // 给fb_info 指针数组中的每一个指针申请分配内存
ctrl->fb[i] = framebuffer_alloc(sizeof(*ctrl->fb),
ctrl->dev);
if (!ctrl->fb[i]) {
dev_err(ctrl->dev, "not enough memory\n");
ret = -ENOMEM;
goto err_alloc_fb;
} s3cfb_init_fbinfo(ctrl, i); // 初始化fb_info 这个结构体 就是去构建fb_info if (i == pdata->default_win) {
if (s3cfb_map_video_memory(ctrl->fb[i])) { // 给FB显存确定内存地址和分配空间(注意只是对默认的fb设备分配了,一个虚拟的显示窗口其实就是抽象为一个fb设备,多个窗口其实是会进行叠加的)
dev_err(ctrl->dev,
"failed to map video memory "
"for default window (%d)\n", i);
ret = -ENOMEM;
goto err_map_video_mem;
}
}
} return ; err_alloc_fb:
while (--i >= ) {
if (i == pdata->default_win)
s3cfb_unmap_default_video_memory(ctrl->fb[i]); err_map_video_mem:
framebuffer_release(ctrl->fb[i]);
}
kfree(ctrl->fb); err_alloc:
return ret;
} struct fb_info *framebuffer_alloc(size_t size, struct device *dev)
{
#define BYTES_PER_LONG (BITS_PER_LONG/8)
#define PADDING (BYTES_PER_LONG - (sizeof(struct fb_info) % BYTES_PER_LONG))
int fb_info_size = sizeof(struct fb_info); // 获取fb_info结构体类型的字节大小
struct fb_info *info;
char *p; if (size)
fb_info_size += PADDING; p = kzalloc(fb_info_size + size, GFP_KERNEL); if (!p)
return NULL; info = (struct fb_info *) p; if (size)
info->par = p + fb_info_size; info->device = dev; // 指定我们的 fb 设备的父类设备是平台设备 /sys/devices/platform/plat_xxxdev/ 这个目录,也就是我们将来创建的设备就在这个目录下 #ifdef CONFIG_FB_BACKLIGHT
mutex_init(&info->bl_curve_mutex);
#endif return info;
#undef PADDING
#undef BYTES_PER_LONG
} static int s3cfb_map_video_memory(struct fb_info *fb)
{
struct fb_fix_screeninfo *fix = &fb->fix;
struct s3cfb_window *win = fb->par;
struct s3cfb_global *fbdev =
platform_get_drvdata(to_platform_device(fb->device));
struct s3c_platform_fb *pdata = to_fb_plat(fbdev->dev); if (win->owner == DMA_MEM_OTHER) {
fix->smem_start = win->other_mem_addr;
fix->smem_len = win->other_mem_size;
return ;
} if (fb->screen_base) // 如果我们之前就已经确定了FB的显存地址的虚拟地址,那么就直接退出,因为这个函数的作用就是给显存确定虚拟内存地址并分配内存空间
return ; if (pdata && pdata->pmem_start && (pdata->pmem_size >= fix->smem_len)) { // 如果我们的平台设备中的私有数据中已经确定了显存的物理地址和大小
fix->smem_start = pdata->pmem_start; // 那么就使用平台设备私有数据中定义的
fb->screen_base = ioremap_wc(fix->smem_start, pdata->pmem_size);
} else
fb->screen_base = dma_alloc_writecombine(fbdev->dev, // 否则的话我们就自己申请分配显存空间
PAGE_ALIGN(fix->smem_len),
(unsigned int *)
&fix->smem_start, GFP_KERNEL); if (!fb->screen_base)
return -ENOMEM; dev_info(fbdev->dev, "[fb%d] dma: 0x%08x, cpu: 0x%08x, "
"size: 0x%08x\n", win->id,
(unsigned int)fix->smem_start,
(unsigned int)fb->screen_base, fix->smem_len); memset(fb->screen_base, , fix->smem_len); // 将FB显存清零
win->owner = DMA_MEM_FIMD; return ;
}
三、platform_device平台设备部分
fb的驱动是基于platform平台总线的,所以需要提供platform_device(注册平台设备)和platform_driver(注册平台驱动)。前面讲的是平台驱动部分
那么它对应的平台设备的注册在什么地方呢? 答案就是之前说的mach文件中,我这里是 arch\arm\mach-s5pv210\mach-x210.c 这个文件。
之前说了,这个文件中注册了很多的系统中可能用到的平台设备,将来写驱动的时候,只需要注册平台驱动即可,当然如果没有,可能就需要自己去添加。
这个文件中将所有的平台设备结构体都放在一个 struct platform_device *类型的数组smdkc110_devices中,将所有定义好的platform_device结构体挂接到这个数组中去,
在 smdkc110_machine_init 函数中将所有的平台设备都进行了注册。 如下: smdkc110_machine_init 这个函数其实是被链接在Linux启动的各个初始化段中的某一个,所以
当系统启动的时候,执行了初始化段中的函数时,smdkc110_machine_init 函数就会被调用。
smdkc110_machine_init
platform_add_devices(smdkc110_devices, ARRAY_SIZE(smdkc110_devices)); // 平台设备的注册
s3cfb_set_platdata(&ek070tn93_fb_data); // 给平台设备设置私有数据
1、struct platform_device s3c_device_fb变量
s3c_device_fb 是fb的平台总线驱动下提供的 platform_device 类型变量,这个变量定义在:arch\arm\plat-s5p\devs.c 文件中
struct platform_device s3c_device_fb = {
.name = "s3cfb", // 平台设备的名字
.id = -,
.num_resources = ARRAY_SIZE(s3cfb_resource), // 平台设备的资源数量
.resource = s3cfb_resource, // 平台设备的资源
.dev = {
.dma_mask = &fb_dma_mask,
.coherent_dma_mask = 0xffffffffUL
}
};
(1)从定义的变量中可以看出来,并没有挂接设备的私有数据到s3c_device_fb变量中,因为platform_device结构体中device结构体下的platform_data指针并没有被赋值
那么是不是这个平台设备没有私有数据呢?
答案是肯定有的,因为前面在分析平台驱动部分时都使用了平台设备的私有数据,那么之前说过,数据有使用的地方,肯定是有产生数据的地方,一定要弄清楚这么一个关系。
那么数据的产生地在那呢? 其实就是在smdkc110_machine_init函数中,这个函数中通过调用另一个函数(s3cfb_set_platdata)来挂接fb平台设备的私有数据。
s3cfb_set_platdata(&ek070tn93_fb_data);
static struct s3c_platform_fb ek070tn93_fb_data __initdata = {
.hw_ver = 0x62,
.nr_wins = ,
.default_win = CONFIG_FB_S3C_DEFAULT_WINDOW, // 默认开启的虚拟窗口
.swap = FB_SWAP_WORD | FB_SWAP_HWORD, .lcd = &ek070tn93, // 描述LCD硬件信息的结构体
.cfg_gpio = ek070tn93_cfg_gpio, // 配置LCD相关的gpio的方法
.backlight_on = ek070tn93_backlight_on, // 使能LCD背光
.backlight_onoff = ek070tn93_backlight_off, // 关闭LCD背光
.reset_lcd = ek070tn93_reset_lcd, // 复位LCD
};
当我们要去移植一款LCD时,一般只需要对这个结构体里面的内容进行的更改,例如 gpio、LCD的硬件信息等等。
1):s3cfb_set_platdata函数分析:
void __init s3cfb_set_platdata(struct s3c_platform_fb *pd)
{
struct s3c_platform_fb *npd; // 定义一个 struct s3c_platform_fb 类型的指针
int i; if (!pd) // 如果没有传入 s3c_platform_fb 结构体变量指针,则使用默认的
pd = &default_fb_data; npd = kmemdup(pd, sizeof(struct s3c_platform_fb), GFP_KERNEL);
if (!npd)
printk(KERN_ERR "%s: no memory for platform data\n", __func__);
else {
for (i = ; i < npd->nr_wins; i++)
npd->nr_buffers[i] = ; npd->nr_buffers[npd->default_win] = CONFIG_FB_S3C_NR_BUFFERS; // 再进一步对数据结构进行填充 s3cfb_get_clk_name(npd->clk_name);
npd->clk_on = s3cfb_clk_on;
npd->clk_off = s3cfb_clk_off; /* starting physical address of memory region */
npd->pmem_start = s5p_get_media_memory_bank(S5P_MDEV_FIMD, );
/* size of memory region */
npd->pmem_size = s5p_get_media_memsize_bank(S5P_MDEV_FIMD, ); s3c_device_fb.dev.platform_data = npd; // 把传进来的 s3c_platform_fb 结构体变量挂载到 s3c_device_fb变量中
}
}
总结: 由上可知s3cfb_set_platdata函数设置平台设备的私有数据,就是定义一个struct s3c_platform_fb类型的指针,然后给他申请分配内存然后进行一系列的填充,
最后将这个结构体挂接到平台设备的私有数据中去。
三星framebuffer驱动代码分析的更多相关文章
-
Linux时间子系统之(十七):ARM generic timer驱动代码分析
专题文档汇总目录 Notes:ARM平台Clock/Timer架构:System counter.Timer以及两者之间关系:Per cpu timer通过CP15访问,System counter通 ...
-
Linux时间子系统(十七) ARM generic timer驱动代码分析
一.前言 关注ARM平台上timer driver(clocksource chip driver和clockevent chip driver)的驱动工程师应该会注意到timer硬件的演化过程.在单 ...
-
platform总线驱动代码分析
/************************************************************************/ Linux内核版本:2.6.35.7 运行平台:三 ...
-
[置顶] 自娱自乐7之Linux UDC驱动2(自编udc驱动,现完成枚举过程,从驱动代码分析枚举过程)
花了半个月,才搞定驱动中的枚举部分,现在说linux的枚举,windows可能有差别. 代码我会贴在后面,现在只是实现枚举,你可能对代码不感兴趣,我就不分析代码了,你可以看看 在<自娱自乐1&g ...
-
基于等待队列及poll机制的按键驱动代码分析和测试代码
按键驱动分析: #include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> ...
-
USB转串口驱动代码分析
1.USB插入时,创建设备 [plain] view plaincopy DriverObject->DriverExtension->AddDevice = USB2COM_PnPAdd ...
-
支持阻塞操作和轮询操作的globalfifo设备驱动代码分析以及测试代码
#include <linux/module.h> #include <linux/types.h> #include <linux/fs.h> #include ...
-
Linux网卡驱动架构分析
一.网卡驱动架构 由上到下层次依次为:应用程序→系统调用接口→协议无关接口→网络协议栈→设备无关接口→设备驱动. 二.重要数据结构 1.Linux内核中每一个网卡由一个net_device结构来描述. ...
-
linux驱动由浅入深系列:高通sensor架构实例分析之二(驱动代码结构)【转】
本文转载自:https://blog.csdn.net/radianceblau/article/details/73498303 本系列导航: linux驱动由浅入深系列:高通sensor架构实例分 ...
随机推荐
-
HDU 5017 Ellipsoid 模拟退火第一题
为了补这题,特意学了下模拟退火算法,感觉算法本身不是很难,就是可能降温系数,步长等参数不好设置. 具体学习可以参见: http://www.cnblogs.com/heaad/archive/2010 ...
-
【Android基础】短信的发送
//Button的点击事件 @Override public void onClick(View v) { // 接受者电话号码 Uri uri = Uri.parse("smsto:123 ...
-
大数据时代日志分析平台ELK的搭建
A,首先说说ELK是啥, ELK是ElasticSearch . Logstash 和 Kiabana 三个开源工具组成.Logstash是数据源,ElasticSearch是分析数据的,Kiaba ...
-
测试开发Python培训:实现屌丝的黄色图片收藏愿望(小插曲)
男学员在学习python的自动化过程中对于爬虫很感兴趣,有些学员就想能收藏一些情色图片,供自己欣赏.作为讲师只能是满足愿望,帮助大家实现对美的追求,http://wanimal.lofter.com/ ...
-
策略模式 Strategy 政策Policy 行为型 设计模式(二十五)
策略模式 Strategy 与策略相关的常见词汇有:营销策略.折扣策略.教学策略.记忆策略.学习策略.... “策略”意味着分情况讨论,而不是一概而论 面对不同年龄段的人,面对不同的商品,必然将会 ...
-
leetcode978
class Solution(object): def maxTurbulenceSize(self, A: 'List[int]') -> int: n = len(A) if n == 1: ...
-
SHELL脚本--多命令逻辑执行顺序
bash&shell系列文章:http://www.cnblogs.com/f-ck-need-u/p/7048359.html Linux中可以使用分号“;”.双and号“&& ...
- asp.net mvc模板布局
-
ABP框架系列之九:(Abp-Session-会话)
Introduction ASP.NET Boilerplate provides IAbpSession interface to obtain current user and tenant wi ...
-
unigui结合JS方法记录
在js中界面上所有组件都当成html里来控制 .控制按钮事件 document.getElementById(MainForm.UniButton4.getId()).click(); 这个方法让J ...