summaryrefslogtreecommitdiff
path: root/linux-core
diff options
context:
space:
mode:
authorJesse Barnes <jbarnes@hobbes.virtuousgeek.org>2007-05-17 09:00:06 -0700
committerJesse Barnes <jbarnes@hobbes.virtuousgeek.org>2007-05-17 09:00:06 -0700
commita18b4befb9b76c4b2662ff6caa0e4f0975eb8e9c (patch)
tree3f4b00c8c4f11f0c78fa64fcf617ea33dede28b6 /linux-core
parentb589b846e73bfe6235cd702bb8ae89701c85eaab (diff)
Fix FB pitch value (we had it wrong and were working around it in a few
places). Add new FB hooks to the drm driver structure and make i915 use them for an Intel specific FB driver. This will allow acceleration and better handling of the command stream.
Diffstat (limited to 'linux-core')
-rw-r--r--linux-core/Makefile.kernel4
-rw-r--r--linux-core/drmP.h4
-rw-r--r--linux-core/drm_crtc.c15
-rw-r--r--linux-core/drm_fb.c6
-rw-r--r--linux-core/i915_drv.c3
-rw-r--r--linux-core/intel_display.c4
-rw-r--r--linux-core/intel_drv.h3
-rw-r--r--linux-core/intel_fb.c358
8 files changed, 381 insertions, 16 deletions
diff --git a/linux-core/Makefile.kernel b/linux-core/Makefile.kernel
index b9684d68..b4fe7fa8 100644
--- a/linux-core/Makefile.kernel
+++ b/linux-core/Makefile.kernel
@@ -14,14 +14,14 @@ drm-objs := drm_auth.o drm_bufs.o drm_context.o drm_dma.o drm_drawable.o \
drm_memory_debug.o ati_pcigart.o drm_sman.o \
drm_hashtab.o drm_mm.o drm_object.o drm_compat.o \
drm_fence.o drm_ttm.o drm_bo.o drm_bo_move.o drm_crtc.o \
- drm_edid.o drm_modes.o drm_fb.o
+ drm_edid.o drm_modes.o
tdfx-objs := tdfx_drv.o
r128-objs := r128_drv.o r128_cce.o r128_state.o r128_irq.o
mga-objs := mga_drv.o mga_dma.o mga_state.o mga_warp.o mga_irq.o
i810-objs := i810_drv.o i810_dma.o
i915-objs := i915_drv.o i915_dma.o i915_irq.o i915_mem.o i915_fence.o \
i915_buffer.o intel_display.o intel_crt.o intel_lvds.o \
- intel_sdvo.o intel_modes.o intel_i2c.o i915_init.o
+ intel_sdvo.o intel_modes.o intel_i2c.o i915_init.o intel_fb.o
nouveau-objs := nouveau_drv.o nouveau_state.o nouveau_fifo.o nouveau_mem.o \
nouveau_object.o nouveau_irq.o \
nv04_timer.o \
diff --git a/linux-core/drmP.h b/linux-core/drmP.h
index 377f447a..2417181d 100644
--- a/linux-core/drmP.h
+++ b/linux-core/drmP.h
@@ -661,6 +661,10 @@ struct drm_driver {
unsigned long (*get_reg_ofs) (struct drm_device * dev);
void (*set_version) (struct drm_device * dev, drm_set_version_t * sv);
+ /* FB routines, if present */
+ int (*fb_probe)(struct drm_device *dev, struct drm_framebuffer *fb);
+ int (*fb_remove)(struct drm_device *dev, struct drm_framebuffer *fb);
+
struct drm_fence_driver *fence_driver;
struct drm_bo_driver *bo_driver;
diff --git a/linux-core/drm_crtc.c b/linux-core/drm_crtc.c
index 16cf62a7..26a1cf2f 100644
--- a/linux-core/drm_crtc.c
+++ b/linux-core/drm_crtc.c
@@ -910,10 +910,8 @@ bool drm_initial_config(drm_device_t *dev, bool can_grow)
/* FB config is max of above desired resolutions */
/* FIXME: per-output FBs/CRTCs */
- if (des_mode->hdisplay > fb->width) {
+ if (des_mode->hdisplay > fb->width)
fb->width = des_mode->hdisplay;
- fb->pitch = fb->width;
- }
if (des_mode->vdisplay > fb->height)
fb->height = des_mode->vdisplay;
}
@@ -921,6 +919,7 @@ bool drm_initial_config(drm_device_t *dev, bool can_grow)
/* FIXME: multiple depths */
bytes_per_pixel = 4;
fb->bits_per_pixel = bytes_per_pixel * 8;
+ fb->pitch = fb->width * ((fb->bits_per_pixel + 1) / 8);
fb->depth = bytes_per_pixel * 8;
size = fb->width * fb->height * bytes_per_pixel;
drm_buffer_object_create(dev, size, drm_bo_type_kernel,
@@ -932,7 +931,7 @@ bool drm_initial_config(drm_device_t *dev, bool can_grow)
fb->height, fbo->offset, fbo);
fb->offset = fbo->offset;
fb->bo = fbo;
- drmfb_probe(dev, fb);
+ dev->driver->fb_probe(dev, fb);
return false;
}
@@ -964,7 +963,7 @@ void drm_mode_config_cleanup(drm_device_t *dev)
}
list_for_each_entry_safe(fb, fbt, &dev->mode_config.fb_list, head) {
- drmfb_remove(dev, fb);
+ dev->driver->fb_remove(dev, fb);
/* If this FB was the kernel one, free it */
if (fb->bo->type == drm_bo_type_kernel) {
mutex_lock(&dev->struct_mutex);
@@ -1528,7 +1527,7 @@ int drm_mode_addfb(struct inode *inode, struct file *filp,
if (copy_to_user(argp, &r, sizeof(r)))
return -EFAULT;
- drmfb_probe(dev, fb);
+ dev->driver->fb_probe(dev, fb);
return 0;
}
@@ -1564,7 +1563,7 @@ int drm_mode_rmfb(struct inode *inode, struct file *filp,
return -EINVAL;
}
- drmfb_remove(dev, fb);
+ dev->driver->fb_remove(dev, fb);
/* TODO check if we own the buffer */
/* TODO release all crtc connected to the framebuffer */
/* bind the fb to the crtc for now */
@@ -1645,7 +1644,7 @@ void drm_fb_release(struct file *filp)
list_for_each_entry_safe(fb, tfb, &priv->fbs, filp_head) {
list_del(&fb->filp_head);
- drmfb_remove(dev, fb);
+ dev->driver->fb_remove(dev, fb);
drm_framebuffer_destroy(fb);
}
diff --git a/linux-core/drm_fb.c b/linux-core/drm_fb.c
index ef05341a..1eb31952 100644
--- a/linux-core/drm_fb.c
+++ b/linux-core/drm_fb.c
@@ -106,8 +106,6 @@ int drmfb_probe(struct drm_device *dev, struct drm_framebuffer *fb)
struct fb_info *info;
struct drmfb_par *par;
struct device *device = &dev->pdev->dev;
- struct fb_var_screeninfo *var_info;
- unsigned long base, size;
int ret;
info = framebuffer_alloc(sizeof(struct drmfb_par), device);
@@ -126,7 +124,7 @@ int drmfb_probe(struct drm_device *dev, struct drm_framebuffer *fb)
strcpy(info->fix.id, "drmfb");
info->fix.smem_start = fb->offset + dev->mode_config.fb_base;
- info->fix.smem_len = (8*1024*1024);
+ info->fix.smem_len = fb->bo->mem.size;
info->fix.type = FB_TYPE_PACKED_PIXELS;
info->fix.visual = FB_VISUAL_DIRECTCOLOR;
info->fix.accel = FB_ACCEL_NONE;
@@ -142,7 +140,7 @@ int drmfb_probe(struct drm_device *dev, struct drm_framebuffer *fb)
DRM_ERROR("error mapping fb: %d\n", ret);
info->screen_base = fb->virtual_base;
- info->screen_size = size;
+ info->screen_size = fb->bo->mem.size;
info->pseudo_palette = fb->pseudo_palette;
info->var.xres = fb->width;
info->var.xres_virtual = fb->pitch;
diff --git a/linux-core/i915_drv.c b/linux-core/i915_drv.c
index 50ff9771..ecf42771 100644
--- a/linux-core/i915_drv.c
+++ b/linux-core/i915_drv.c
@@ -30,6 +30,7 @@
#include "drmP.h"
#include "drm.h"
#include "i915_drm.h"
+#include "intel_drv.h"
#include "i915_drv.h"
#include "drm_pciids.h"
@@ -92,6 +93,8 @@ static struct drm_driver driver = {
.reclaim_buffers = drm_core_reclaim_buffers,
.get_map_ofs = drm_core_get_map_ofs,
.get_reg_ofs = drm_core_get_reg_ofs,
+ .fb_probe = intelfb_probe,
+ .fb_remove = intelfb_remove,
.ioctls = i915_ioctls,
.fops = {
.owner = THIS_MODULE,
diff --git a/linux-core/intel_display.c b/linux-core/intel_display.c
index 7d581175..be2db912 100644
--- a/linux-core/intel_display.c
+++ b/linux-core/intel_display.c
@@ -370,7 +370,7 @@ intel_pipe_set_base(struct drm_crtc *crtc, int x, int y)
int dspsurf = (pipe == 0 ? DSPASURF : DSPBSURF);
Start = crtc->fb->offset;
- Offset = ((y * crtc->fb->pitch + x) * (crtc->fb->bits_per_pixel / 8));
+ Offset = y * crtc->fb->pitch + x;
DRM_DEBUG("Writing base %08lX %08lX %d %d\n", Start, Offset, x, y);
if (IS_I965G(dev)) {
@@ -911,7 +911,7 @@ static void intel_crtc_mode_set(struct drm_crtc *crtc,
((adjusted_mode->crtc_vblank_end - 1) << 16));
I915_WRITE(vsync_reg, (adjusted_mode->crtc_vsync_start - 1) |
((adjusted_mode->crtc_vsync_end - 1) << 16));
- I915_WRITE(dspstride_reg, crtc->fb->pitch * (crtc->fb->bits_per_pixel / 8));
+ I915_WRITE(dspstride_reg, crtc->fb->pitch);
/* pipesrc and dspsize control the size that is scaled from, which should
* always be the user's requested size.
*/
diff --git a/linux-core/intel_drv.h b/linux-core/intel_drv.h
index aa33437d..91112ff5 100644
--- a/linux-core/intel_drv.h
+++ b/linux-core/intel_drv.h
@@ -76,4 +76,7 @@ extern struct drm_display_mode *intel_crtc_mode_get(drm_device_t *dev,
extern void intel_wait_for_vblank(drm_device_t *dev);
extern struct drm_crtc *intel_get_crtc_from_pipe(drm_device_t *dev, int pipe);
+extern int intelfb_probe(struct drm_device *dev, struct drm_framebuffer *fb);
+extern int intelfb_remove(struct drm_device *dev, struct drm_framebuffer *fb);
+
#endif /* __INTEL_DRV_H__ */
diff --git a/linux-core/intel_fb.c b/linux-core/intel_fb.c
new file mode 100644
index 00000000..267b4fd6
--- /dev/null
+++ b/linux-core/intel_fb.c
@@ -0,0 +1,358 @@
+/*
+ * Copyright © 2007 David Airlie
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ * David Airlie
+ */
+ /*
+ * Modularization
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/tty.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/fb.h>
+#include <linux/init.h>
+
+#include "drmP.h"
+#include "drm.h"
+#include "i915_drm.h"
+#include "i915_drv.h"
+
+struct intelfb_par {
+ struct drm_device *dev;
+ struct drm_framebuffer *fb;
+};
+
+static int intelfb_setcolreg(unsigned regno, unsigned red, unsigned green,
+ unsigned blue, unsigned transp,
+ struct fb_info *info)
+{
+ struct intelfb_par *par = info->par;
+ struct drm_framebuffer *fb = par->fb;
+ if (regno > 17)
+ return 1;
+
+ if (regno < 16) {
+ switch (fb->depth) {
+ case 15:
+ fb->pseudo_palette[regno] = ((red & 0xf800) >> 1) |
+ ((green & 0xf800) >> 6) |
+ ((blue & 0xf800) >> 11);
+ break;
+ case 16:
+ fb->pseudo_palette[regno] = (red & 0xf800) |
+ ((green & 0xfc00) >> 5) |
+ ((blue & 0xf800) >> 11);
+ break;
+ case 24:
+ case 32:
+ fb->pseudo_palette[regno] = ((red & 0xff00) << 8) |
+ (green & 0xff00) |
+ ((blue & 0xff00) >> 8);
+ break;
+ }
+ }
+
+ return 0;
+}
+
+/* this will let fbcon do the mode init */
+static int intelfb_set_par(struct fb_info *info)
+{
+ struct intelfb_par *par = info->par;
+ struct drm_device *dev = par->dev;
+
+ drm_set_desired_modes(dev);
+ return 0;
+}
+
+static void intelfb_copyarea(struct fb_info *info,
+ const struct fb_copyarea *region)
+{
+ struct intelfb_par *par = info->par;
+ struct drm_device *dev = par->dev;
+ struct drm_i915_private *dev_priv = dev->dev_private;
+ u32 src_x1, src_y1, dst_x1, dst_y1, dst_x2, dst_y2, offset;
+ u32 cmd, rop_depth_pitch, src_pitch;
+ RING_LOCALS;
+
+ cmd = XY_SRC_COPY_BLT_CMD;
+ src_x1 = region->sx;
+ src_y1 = region->sy;
+ dst_x1 = region->dx;
+ dst_y1 = region->dy;
+ dst_x2 = region->dx + region->width;
+ dst_y2 = region->dy + region->height;
+ offset = par->fb->offset;
+ rop_depth_pitch = BLT_ROP_GXCOPY | par->fb->pitch;
+ src_pitch = par->fb->pitch;
+
+ switch (par->fb->bits_per_pixel) {
+ case 16:
+ rop_depth_pitch |= BLT_DEPTH_16_565;
+ break;
+ case 32:
+ rop_depth_pitch |= BLT_DEPTH_32;
+ cmd |= XY_SRC_COPY_BLT_WRITE_ALPHA | XY_SRC_COPY_BLT_WRITE_RGB;
+ break;
+ }
+
+ BEGIN_LP_RING(8);
+ OUT_RING(cmd);
+ OUT_RING(rop_depth_pitch);
+ OUT_RING((dst_y1 << 16) | (dst_x1 & 0xffff));
+ OUT_RING((dst_y2 << 16) | (dst_x2 & 0xffff));
+ OUT_RING(offset);
+ OUT_RING((src_y1 << 16) | (src_x1 & 0xffff));
+ OUT_RING(src_pitch);
+ OUT_RING(offset);
+ ADVANCE_LP_RING();
+}
+
+#define ROUND_UP_TO(x, y) (((x) + (y) - 1) / (y) * (y))
+#define ROUND_DOWN_TO(x, y) ((x) / (y) * (y))
+
+void intelfb_imageblit(struct fb_info *info, const struct fb_image *image)
+{
+ struct intelfb_par *par = info->par;
+ struct drm_device *dev = par->dev;
+ struct drm_i915_private *dev_priv = dev->dev_private;
+ u32 cmd, rop_pitch_depth, tmp;
+ int nbytes, ndwords, pad;
+ u32 dst_x1, dst_y1, dst_x2, dst_y2, offset, bg, fg;
+ int dat, ix, iy, iw;
+ int i, j;
+ RING_LOCALS;
+
+ /* size in bytes of a padded scanline */
+ nbytes = ROUND_UP_TO(image->width, 16) / 8;
+
+ /* Total bytes of padded scanline data to write out. */
+ nbytes *= image->height;
+
+ /*
+ * Check if the glyph data exceeds the immediate mode limit.
+ * It would take a large font (1K pixels) to hit this limit.
+ */
+ if (nbytes > 128 || image->depth != 1)
+ return cfb_imageblit(info, image);
+
+ /* Src data is packaged a dword (32-bit) at a time. */
+ ndwords = ROUND_UP_TO(nbytes, 4) / 4;
+
+ /*
+ * Ring has to be padded to a quad word. But because the command starts
+ with 7 bytes, pad only if there is an even number of ndwords
+ */
+ pad = !(ndwords % 2);
+
+ DRM_DEBUG("imageblit %dx%dx%d to (%d,%d)\n", image->width,
+ image->height, image->depth, image->dx, image->dy);
+ DRM_DEBUG("nbytes: %d, ndwords: %d, pad: %d\n", nbytes, ndwords, pad);
+
+ tmp = (XY_MONO_SRC_COPY_IMM_BLT & 0xff) + ndwords;
+ cmd = (XY_MONO_SRC_COPY_IMM_BLT & ~0xff) | tmp;
+ offset = par->fb->offset;
+ dst_x1 = image->dx;
+ dst_y1 = image->dy;
+ dst_x2 = image->dx + image->width;
+ dst_y2 = image->dy + image->height;
+ rop_pitch_depth = BLT_ROP_GXCOPY | par->fb->pitch;
+
+ switch (par->fb->bits_per_pixel) {
+ case 8:
+ rop_pitch_depth |= BLT_DEPTH_8;
+ fg = image->fg_color;
+ bg = image->bg_color;
+ break;
+ case 16:
+ rop_pitch_depth |= BLT_DEPTH_16_565;
+ fg = par->fb->pseudo_palette[image->fg_color];
+ bg = par->fb->pseudo_palette[image->bg_color];
+ break;
+ case 32:
+ rop_pitch_depth |= BLT_DEPTH_32;
+ cmd |= XY_SRC_COPY_BLT_WRITE_ALPHA | XY_SRC_COPY_BLT_WRITE_RGB;
+ fg = par->fb->pseudo_palette[image->fg_color];
+ bg = par->fb->pseudo_palette[image->bg_color];
+ break;
+ default:
+ DRM_ERROR("unknown depth %d\n", par->fb->bits_per_pixel);
+ break;
+ }
+
+ BEGIN_LP_RING(8 + ndwords);
+ OUT_RING(cmd);
+ OUT_RING(rop_pitch_depth);
+ OUT_RING((dst_y1 << 16) | (dst_x1 & 0xffff));
+ OUT_RING((dst_y2 << 16) | (dst_x2 & 0xffff));
+ OUT_RING(offset);
+ OUT_RING(bg);
+ OUT_RING(fg);
+ ix = iy = 0;
+ iw = ROUND_UP_TO(image->width, 8) / 8;
+ while (ndwords--) {
+ dat = 0;
+ for (j = 0; j < 2; ++j) {
+ for (i = 0; i < 2; ++i) {
+ if (ix != iw || i == 0)
+ dat |= image->data[iy*iw + ix++] << (i+j*2)*8;
+ }
+ if (ix == iw && iy != (image->height - 1)) {
+ ix = 0;
+ ++iy;
+ }
+ }
+ OUT_RING(dat);
+ }
+ if (pad)
+ OUT_RING(MI_NOOP);
+ ADVANCE_LP_RING();
+}
+
+static struct fb_ops intelfb_ops = {
+ .owner = THIS_MODULE,
+ // .fb_open = intelfb_open,
+ // .fb_read = intelfb_read,
+ // .fb_write = intelfb_write,
+ // .fb_release = intelfb_release,
+ // .fb_ioctl = intelfb_ioctl,
+ .fb_set_par = intelfb_set_par,
+ .fb_setcolreg = intelfb_setcolreg,
+ .fb_fillrect = cfb_fillrect,
+ .fb_copyarea = cfb_copyarea, //intelfb_copyarea,
+ .fb_imageblit = intelfb_imageblit,
+};
+
+int intelfb_probe(struct drm_device *dev, struct drm_framebuffer *fb)
+{
+ struct fb_info *info;
+ struct intelfb_par *par;
+ struct device *device = &dev->pdev->dev;
+ int ret;
+
+ info = framebuffer_alloc(sizeof(struct intelfb_par), device);
+ if (!info){
+ return -EINVAL;
+ }
+
+ fb->fbdev = info;
+
+ par = info->par;
+
+ par->dev = dev;
+ par->fb = fb;
+
+ info->fbops = &intelfb_ops;
+
+ strcpy(info->fix.id, "intelfb");
+ info->fix.smem_start = fb->offset + dev->mode_config.fb_base;
+ info->fix.smem_len = fb->bo->mem.size;
+ info->fix.type = FB_TYPE_PACKED_PIXELS;
+ info->fix.type_aux = 0;
+ info->fix.xpanstep = 8;
+ info->fix.ypanstep = 1;
+ info->fix.ywrapstep = 0;
+ info->fix.visual = FB_VISUAL_DIRECTCOLOR;
+ info->fix.accel = FB_ACCEL_I830;
+ info->fix.type_aux = 0;
+ info->fix.mmio_start = 0;
+ info->fix.mmio_len = 0;
+ info->fix.line_length = fb->pitch;
+
+ info->flags = FBINFO_DEFAULT;
+
+ ret = drm_mem_reg_ioremap(dev, &fb->bo->mem, &fb->virtual_base);
+ if (ret)
+ DRM_ERROR("error mapping fb: %d\n", ret);
+
+ info->screen_base = fb->virtual_base;
+ info->screen_size = fb->bo->mem.size;
+ info->pseudo_palette = fb->pseudo_palette;
+ info->var.xres = fb->width;
+ info->var.xres_virtual = fb->width;
+ info->var.yres = fb->height;
+ info->var.yres_virtual = fb->height;
+ info->var.bits_per_pixel = fb->bits_per_pixel;
+ info->var.xoffset = 0;
+ info->var.yoffset = 0;
+ info->var.activate = FB_ACTIVATE_NOW;
+ info->var.height = -1;
+ info->var.width = -1;
+ info->var.vmode = FB_VMODE_NONINTERLACED;
+
+ info->pixmap.size = 64*1024;
+ info->pixmap.buf_align = 8;
+ info->pixmap.access_align = 32;
+ info->pixmap.flags = FB_PIXMAP_SYSTEM;
+ info->pixmap.scan_align = 1;
+
+ DRM_DEBUG("fb depth is %d\n", fb->depth);
+ DRM_DEBUG(" pitch is %d\n", fb->pitch);
+ switch(fb->depth) {
+ case 8:
+ case 15:
+ case 16:
+ break;
+ default:
+ case 24:
+ case 32:
+ info->var.red.offset = 16;
+ info->var.green.offset = 8;
+ info->var.blue.offset = 0;
+ info->var.red.length = info->var.green.length =
+ info->var.blue.length = 8;
+ if (fb->depth == 32) {
+ info->var.transp.offset = 24;
+ info->var.transp.length = 8;
+ }
+ break;
+ }
+
+ if (register_framebuffer(info) < 0)
+ return -EINVAL;
+
+ printk(KERN_INFO "fb%d: %s frame buffer device\n", info->node,
+ info->fix.id);
+ return 0;
+}
+EXPORT_SYMBOL(intelfb_probe);
+
+int intelfb_remove(struct drm_device *dev, struct drm_framebuffer *fb)
+{
+ struct fb_info *info = fb->fbdev;
+
+ if (info) {
+ unregister_framebuffer(info);
+ framebuffer_release(info);
+ drm_mem_reg_iounmap(dev, &fb->bo->mem, fb->virtual_base);
+ }
+ return 0;
+}
+EXPORT_SYMBOL(intelfb_remove);
+MODULE_LICENSE("GPL");