diff --git a/hw/kdrive/fbdev/Xfbdev.man b/hw/kdrive/fbdev/Xfbdev.man new file mode 100644 index 0000000000..ab83867f86 --- /dev/null +++ b/hw/kdrive/fbdev/Xfbdev.man @@ -0,0 +1,28 @@ +.\" $RCSId: xc/programs/Xserver/hw/kdrive/fbdev/Xfbdev.man,v 1.4 2001/01/27 18:20:40 dawes Exp $ +.\" +.TH Xfbdev 1 __vendorversion__ +.SH NAME +Xfbdev \- Linux framebuffer device tiny X server +.SH SYNOPSIS +.B Xfbdev +.RI [ :display ] +.RI [ option ...] +.SH DESCRIPTION +.B Xfbdev +is a generic X server for Linux. +.B Xfbdev +doesn't know about any particular hardware, and uses the framebuffer +provided by the Linux framebuffer device. +.SH OPTIONS +.B Xfbdev +accepts the common options of the Xkdrive family of servers. Please +see Xkdrive(1). +.SH KEYBOARD +To be written. +.SH SEE ALSO +X(__miscmansuffix__), Xserver(1), Xkdrive(1), xdm(1), xinit(1). +.SH AUTHORS +The +.B Xfbdev +server was written by Keith Packard. + diff --git a/hw/kdrive/fbdev/fbdev.c b/hw/kdrive/fbdev/fbdev.c new file mode 100644 index 0000000000..d55486a118 --- /dev/null +++ b/hw/kdrive/fbdev/fbdev.c @@ -0,0 +1,895 @@ +/* + * Copyright © 1999 Keith Packard + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of Keith Packard not be used in + * advertising or publicity pertaining to distribution of the software without + * specific, written prior permission. Keith Packard makes no + * representations about the suitability of this software for any purpose. It + * is provided "as is" without express or implied warranty. + * + * KEITH PACKARD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL KEITH PACKARD BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include "fbdev.h" +#include "fb/fb_priv.h" +#include + +#include + +#ifndef xallocarray +#define xallocarray(num, size) reallocarray(NULL, (num), (size)) +#endif + +const char *fbdevDevicePath = NULL; + +static Bool +fbdevInitialize(KdCardInfo * card, FbdevPriv * priv) +{ + unsigned long off; + + if (fbdevDevicePath == NULL) + fbdevDevicePath = "/dev/fb0"; + + if ((priv->fd = open(fbdevDevicePath, O_RDWR)) < 0) { + ErrorF("Error opening framebuffer %s: %s\n", + fbdevDevicePath, strerror(errno)); + return FALSE; + } + + /* quiet valgrind */ + memset(&priv->fix, '\0', sizeof(priv->fix)); + if (ioctl(priv->fd, FBIOGET_FSCREENINFO, &priv->fix) < 0) { + perror("Error with /dev/fb ioctl FIOGET_FSCREENINFO"); + close(priv->fd); + return FALSE; + } + /* quiet valgrind */ + memset(&priv->var, '\0', sizeof(priv->var)); + if (ioctl(priv->fd, FBIOGET_VSCREENINFO, &priv->var) < 0) { + perror("Error with /dev/fb ioctl FIOGET_VSCREENINFO"); + close(priv->fd); + return FALSE; + } + + priv->fb_base = (char *) mmap((caddr_t) NULL, + priv->fix.smem_len, + PROT_READ | PROT_WRITE, + MAP_SHARED, priv->fd, 0); + + if (priv->fb_base == (char *) -1) { + perror("ERROR: mmap framebuffer fails!"); + close(priv->fd); + return FALSE; + } + off = (unsigned long) priv->fix.smem_start % (unsigned long) getpagesize(); + priv->fb = priv->fb_base + off; + return TRUE; +} + +Bool +fbdevCardInit(KdCardInfo * card) +{ + FbdevPriv *priv; + + priv = (FbdevPriv *) malloc(sizeof(FbdevPriv)); + if (!priv) + return FALSE; + + if (!fbdevInitialize(card, priv)) { + free(priv); + return FALSE; + } + card->driver = priv; + + return TRUE; +} + +static Pixel +fbdevMakeContig(Pixel orig, Pixel others) +{ + Pixel low; + + low = lowbit(orig) >> 1; + while (low && (others & low) == 0) { + orig |= low; + low >>= 1; + } + return orig; +} + +static Bool +fbdevModeSupported(KdScreenInfo * screen, const KdMonitorTiming * t) +{ + return TRUE; +} + +static void +fbdevConvertMonitorTiming(const KdMonitorTiming * t, + struct fb_var_screeninfo *var) +{ + memset(var, 0, sizeof(struct fb_var_screeninfo)); + + var->xres = t->horizontal; + var->yres = t->vertical; + var->xres_virtual = t->horizontal; + var->yres_virtual = t->vertical; + var->xoffset = 0; + var->yoffset = 0; + var->pixclock = t->clock ? 1000000000 / t->clock : 0; + var->left_margin = t->hbp; + var->right_margin = t->hfp; + var->upper_margin = t->vbp; + var->lower_margin = t->vfp; + var->hsync_len = t->hblank - t->hfp - t->hbp; + var->vsync_len = t->vblank - t->vfp - t->vbp; + + var->sync = 0; + var->vmode = 0; + + if (t->hpol == KdSyncPositive) + var->sync |= FB_SYNC_HOR_HIGH_ACT; + if (t->vpol == KdSyncPositive) + var->sync |= FB_SYNC_VERT_HIGH_ACT; +} + +static Bool +fbdevScreenInitialize(KdScreenInfo * screen, FbdevScrPriv * scrpriv) +{ + FbdevPriv *priv = screen->card->driver; + Pixel allbits; + int depth; + Bool gray; + struct fb_var_screeninfo var; + const KdMonitorTiming *t; + int k; + + k = ioctl(priv->fd, FBIOGET_VSCREENINFO, &var); + + if (!screen->width || !screen->height) { + if (k >= 0) { + screen->width = var.xres; + screen->height = var.yres; + } + else { + screen->width = 1024; + screen->height = 768; + } + screen->rate = 103; /* FIXME: should get proper value from fb driver */ + } + if (!screen->fb.depth) { + if (k >= 0) + screen->fb.depth = var.bits_per_pixel; + else + screen->fb.depth = 16; + } + + if ((screen->width != var.xres) || (screen->height != var.yres)) { + t = KdFindMode(screen, fbdevModeSupported); + screen->rate = t->rate; + screen->width = t->horizontal; + screen->height = t->vertical; + + /* Now try setting the mode */ + if (k < 0 || (t->horizontal != var.xres || t->vertical != var.yres)) + fbdevConvertMonitorTiming(t, &var); + } + + var.activate = FB_ACTIVATE_NOW; + var.bits_per_pixel = screen->fb.depth; + var.nonstd = 0; + var.grayscale = 0; + + k = ioctl(priv->fd, FBIOPUT_VSCREENINFO, &var); + + if (k < 0) { + fprintf(stderr, "error: %s\n", strerror(errno)); + return FALSE; + } + + /* Re-get the "fixed" parameters since they might have changed */ + k = ioctl(priv->fd, FBIOGET_FSCREENINFO, &priv->fix); + if (k < 0) + perror("FBIOGET_FSCREENINFO"); + + /* Now get the new screeninfo */ + ioctl(priv->fd, FBIOGET_VSCREENINFO, &priv->var); + depth = priv->var.bits_per_pixel; + gray = priv->var.grayscale; + + /* Calculate fix.line_length if it's zero */ + if (!priv->fix.line_length) + priv->fix.line_length = (priv->var.xres_virtual * depth + 7) / 8; + + switch (priv->fix.visual) { + case FB_VISUAL_MONO01: + case FB_VISUAL_MONO10: + screen->fb.visuals = (1 << StaticGray); + break; + case FB_VISUAL_PSEUDOCOLOR: + screen->fb.visuals = (1 << StaticGray); + if (priv->var.bits_per_pixel == 1) { + /* Override to monochrome, to have preallocated black/white */ + priv->fix.visual = FB_VISUAL_MONO01; + } else if (gray) { + /* could also support GrayScale, but what's the point? */ + } else { + screen->fb.visuals = ((1 << StaticGray) | + (1 << GrayScale) | + (1 << StaticColor) | + (1 << PseudoColor) | + (1 << TrueColor) | (1 << DirectColor)); + } + screen->fb.blueMask = 0x00; + screen->fb.greenMask = 0x00; + screen->fb.redMask = 0x00; + break; + case FB_VISUAL_STATIC_PSEUDOCOLOR: + if (gray) { + screen->fb.visuals = (1 << StaticGray); + } + else { + screen->fb.visuals = (1 << StaticColor); + } + screen->fb.blueMask = 0x00; + screen->fb.greenMask = 0x00; + screen->fb.redMask = 0x00; + break; + case FB_VISUAL_TRUECOLOR: + case FB_VISUAL_DIRECTCOLOR: + screen->fb.visuals = (1 << TrueColor); +#define Mask(o,l) (((1 << l) - 1) << o) + screen->fb.redMask = Mask (priv->var.red.offset, priv->var.red.length); + screen->fb.greenMask = + Mask (priv->var.green.offset, priv->var.green.length); + screen->fb.blueMask = + Mask (priv->var.blue.offset, priv->var.blue.length); + + /* + * This is a kludge so that Render will work -- fill in the gaps + * in the pixel + */ + screen->fb.redMask = fbdevMakeContig(screen->fb.redMask, + screen->fb.greenMask | + screen->fb.blueMask); + + screen->fb.greenMask = fbdevMakeContig(screen->fb.greenMask, + screen->fb.redMask | + screen->fb.blueMask); + + screen->fb.blueMask = fbdevMakeContig(screen->fb.blueMask, + screen->fb.redMask | + screen->fb.greenMask); + + allbits = + screen->fb.redMask | screen->fb.greenMask | screen->fb.blueMask; + depth = 32; + while (depth && !(allbits & (1 << (depth - 1)))) + depth--; + break; + default: + return FALSE; + break; + } + screen->fb.depth = depth; + screen->fb.bitsPerPixel = priv->var.bits_per_pixel; + + scrpriv->randr = screen->randr; + + return fbdevMapFramebuffer(screen); +} + +Bool +fbdevScreenInit(KdScreenInfo * screen) +{ + FbdevScrPriv *scrpriv; + + scrpriv = calloc(1, sizeof(FbdevScrPriv)); + if (!scrpriv) + return FALSE; + screen->driver = scrpriv; + if (!fbdevScreenInitialize(screen, scrpriv)) { + screen->driver = 0; + free(scrpriv); + return FALSE; + } + return TRUE; +} + +static void * +fbdevWindowLinear(ScreenPtr pScreen, + CARD32 row, + CARD32 offset, int mode, CARD32 *size, void *closure) +{ + KdScreenPriv(pScreen); + FbdevPriv *priv = pScreenPriv->card->driver; + + if (!pScreenPriv->enabled) + return 0; + *size = priv->fix.line_length; + return (CARD8 *) priv->fb + row * priv->fix.line_length + offset; +} + +static void * +fbdevWindowAfb(ScreenPtr pScreen, + CARD32 row, + CARD32 offset, int mode, CARD32 *size, void *closure) +{ + KdScreenPriv(pScreen); + FbdevPriv *priv = pScreenPriv->card->driver; + + if (!pScreenPriv->enabled) + return 0; + /* offset to next plane */ + *size = priv->var.yres_virtual * priv->fix.line_length; + return (CARD8 *) priv->fb + row * priv->fix.line_length + offset; +} + +Bool +fbdevMapFramebuffer(KdScreenInfo * screen) +{ + FbdevScrPriv *scrpriv = screen->driver; + KdPointerMatrix m; + FbdevPriv *priv = screen->card->driver; + + if (scrpriv->randr != RR_Rotate_0 || + priv->fix.type != FB_TYPE_PACKED_PIXELS) + scrpriv->shadow = TRUE; + else + scrpriv->shadow = FALSE; + + KdComputePointerMatrix(&m, scrpriv->randr, screen->width, screen->height); + + KdSetPointerMatrix(&m); + + screen->width = priv->var.xres; + screen->height = priv->var.yres; + + if (scrpriv->shadow) { + if (!KdShadowFbAlloc(screen, + scrpriv->randr & (RR_Rotate_90 | RR_Rotate_270))) + return FALSE; + } + else { + screen->fb.byteStride = priv->fix.line_length; + screen->fb.pixelStride = (priv->fix.line_length * 8 / + priv->var.bits_per_pixel); + screen->fb.frameBuffer = (CARD8 *) (priv->fb); + } + + return TRUE; +} + +static void +fbdevSetScreenSizes(ScreenPtr pScreen) +{ + KdScreenPriv(pScreen); + KdScreenInfo *screen = pScreenPriv->screen; + FbdevScrPriv *scrpriv = screen->driver; + FbdevPriv *priv = screen->card->driver; + + if (scrpriv->randr & (RR_Rotate_0 | RR_Rotate_180)) { + pScreen->width = priv->var.xres; + pScreen->height = priv->var.yres; + pScreen->mmWidth = screen->width_mm; + pScreen->mmHeight = screen->height_mm; + } + else { + pScreen->width = priv->var.yres; + pScreen->height = priv->var.xres; + pScreen->mmWidth = screen->height_mm; + pScreen->mmHeight = screen->width_mm; + } +} + +static Bool +fbdevUnmapFramebuffer(KdScreenInfo * screen) +{ + KdShadowFbFree(screen); + return TRUE; +} + +static Bool +fbdevSetShadow(ScreenPtr pScreen) +{ + KdScreenPriv(pScreen); + KdScreenInfo *screen = pScreenPriv->screen; + FbdevScrPriv *scrpriv = screen->driver; + FbdevPriv *priv = screen->card->driver; + ShadowUpdateProc update; + ShadowWindowProc window; + int useYX = 0; + +#ifdef __arm__ + /* Use variant copy routines that always read left to right in the + shadow framebuffer. Reading vertical strips is exceptionally + slow on XScale due to cache effects. */ + useYX = 1; +#endif + + window = fbdevWindowLinear; + update = 0; + switch (priv->fix.type) { + case FB_TYPE_PACKED_PIXELS: + if (scrpriv->randr) + if (priv->var.bits_per_pixel == 16) { + switch (scrpriv->randr) { + case RR_Rotate_90: + if (useYX) + update = shadowUpdateRotate16_90YX; + else + update = shadowUpdateRotate16_90; + break; + case RR_Rotate_180: + update = shadowUpdateRotate16_180; + break; + case RR_Rotate_270: + if (useYX) + update = shadowUpdateRotate16_270YX; + else + update = shadowUpdateRotate16_270; + break; + default: + update = shadowUpdateRotate16; + break; + } + } + else + update = shadowUpdateRotatePacked; + else + update = shadowUpdatePacked; + break; + + case FB_TYPE_PLANES: + window = fbdevWindowAfb; + switch (priv->var.bits_per_pixel) { + case 4: + update = shadowUpdateAfb4; + break; + + case 8: + update = shadowUpdateAfb8; + break; + + default: + FatalError("Bitplanes with bpp %u are not yet supported\n", + priv->var.bits_per_pixel); + } + break; + + case FB_TYPE_INTERLEAVED_PLANES: + if (priv->fix.type_aux == 2) { + switch (priv->var.bits_per_pixel) { + case 4: + update = shadowUpdateIplan2p4; + break; + + case 8: + update = shadowUpdateIplan2p8; + break; + + default: + FatalError("Atari interleaved bitplanes with bpp %u are not yet supported\n", + priv->var.bits_per_pixel); + } + } else { + FatalError("Interleaved bitplanes with interleave %u are not yet supported\n", + priv->fix.type_aux); + } + break; + + case FB_TYPE_TEXT: + FatalError("Text frame buffers are not yet supported\n"); + break; + + case FB_TYPE_VGA_PLANES: + FatalError("VGA planes are not yet supported\n"); + break; + + default: + FatalError("Unsupported frame buffer type %u\n", priv->fix.type); + break; + } + + return KdShadowSet(pScreen, scrpriv->randr, update, window); +} + +#ifdef RANDR +static Bool +fbdevRandRGetInfo(ScreenPtr pScreen, Rotation * rotations) +{ + KdScreenPriv(pScreen); + KdScreenInfo *screen = pScreenPriv->screen; + FbdevScrPriv *scrpriv = screen->driver; + RRScreenSizePtr pSize; + Rotation randr; + int n; + + *rotations = RR_Rotate_All | RR_Reflect_All; + + for (n = 0; n < pScreen->numDepths; n++) + if (pScreen->allowedDepths[n].numVids) + break; + if (n == pScreen->numDepths) + return FALSE; + + pSize = RRRegisterSize(pScreen, + screen->width, + screen->height, screen->width_mm, screen->height_mm); + + randr = KdSubRotation(scrpriv->randr, screen->randr); + + RRSetCurrentConfig(pScreen, randr, 0, pSize); + + return TRUE; +} + +static Bool +fbdevRandRSetConfig(ScreenPtr pScreen, + Rotation randr, int rate, RRScreenSizePtr pSize) +{ + KdScreenPriv(pScreen); + KdScreenInfo *screen = pScreenPriv->screen; + FbdevScrPriv *scrpriv = screen->driver; + Bool wasEnabled = pScreenPriv->enabled; + FbdevScrPriv oldscr; + int oldwidth; + int oldheight; + int oldmmwidth; + int oldmmheight; + int newwidth, newheight, newmmwidth, newmmheight; + + if (screen->randr & (RR_Rotate_0 | RR_Rotate_180)) { + newwidth = pSize->width; + newheight = pSize->height; + newmmwidth = pSize->mmWidth; + newmmheight = pSize->mmHeight; + } + else { + newwidth = pSize->height; + newheight = pSize->width; + newmmwidth = pSize->mmHeight; + newmmheight = pSize->mmWidth; + } + + if (wasEnabled) + KdDisableScreen(pScreen); + + oldscr = *scrpriv; + + oldwidth = screen->width; + oldheight = screen->height; + oldmmwidth = pScreen->mmWidth; + oldmmheight = pScreen->mmHeight; + + /* + * Set new configuration + */ + + scrpriv->randr = KdAddRotation(screen->randr, randr); + pScreen->width = newwidth; + pScreen->height = newheight; + pScreen->mmWidth = newmmwidth; + pScreen->mmHeight = newmmheight; + + fbdevUnmapFramebuffer(screen); + + if (!fbdevMapFramebuffer(screen)) + goto bail4; + + KdShadowUnset(screen->pScreen); + + if (!fbdevSetShadow(screen->pScreen)) + goto bail4; + + fbdevSetScreenSizes(screen->pScreen); + + /* + * Set frame buffer mapping + */ + (*pScreen->ModifyPixmapHeader) (fbGetScreenPixmap(pScreen), + pScreen->width, + pScreen->height, + screen->fb.depth, + screen->fb.bitsPerPixel, + screen->fb.byteStride, + screen->fb.frameBuffer); + + /* set the subpixel order */ + + KdSetSubpixelOrder(pScreen, scrpriv->randr); + if (wasEnabled) + KdEnableScreen(pScreen); + + return TRUE; + + bail4: + fbdevUnmapFramebuffer(screen); + *scrpriv = oldscr; + (void) fbdevMapFramebuffer(screen); + pScreen->width = oldwidth; + pScreen->height = oldheight; + pScreen->mmWidth = oldmmwidth; + pScreen->mmHeight = oldmmheight; + + if (wasEnabled) + KdEnableScreen(pScreen); + return FALSE; +} + +static Bool +fbdevRandRInit(ScreenPtr pScreen) +{ + rrScrPrivPtr pScrPriv; + + if (!RRScreenInit(pScreen)) + return FALSE; + + pScrPriv = rrGetScrPriv(pScreen); + pScrPriv->rrGetInfo = fbdevRandRGetInfo; + pScrPriv->rrSetConfig = fbdevRandRSetConfig; + return TRUE; +} +#endif + +static Bool +fbdevCreateColormap(ColormapPtr pmap) +{ + ScreenPtr pScreen = pmap->pScreen; + + KdScreenPriv(pScreen); + FbdevPriv *priv = pScreenPriv->card->driver; + VisualPtr pVisual; + int i; + int nent; + xColorItem *pdefs; + + switch (priv->fix.visual) { + case FB_VISUAL_MONO01: + pScreen->whitePixel = 0; + pScreen->blackPixel = 1; + pmap->red[0].co.local.red = 65535; + pmap->red[0].co.local.green = 65535; + pmap->red[0].co.local.blue = 65535; + pmap->red[1].co.local.red = 0; + pmap->red[1].co.local.green = 0; + pmap->red[1].co.local.blue = 0; + return TRUE; + case FB_VISUAL_MONO10: + pScreen->blackPixel = 0; + pScreen->whitePixel = 1; + pmap->red[0].co.local.red = 0; + pmap->red[0].co.local.green = 0; + pmap->red[0].co.local.blue = 0; + pmap->red[1].co.local.red = 65535; + pmap->red[1].co.local.green = 65535; + pmap->red[1].co.local.blue = 65535; + return TRUE; + case FB_VISUAL_STATIC_PSEUDOCOLOR: + pVisual = pmap->pVisual; + nent = pVisual->ColormapEntries; + pdefs = xallocarray(nent, sizeof(xColorItem)); + if (!pdefs) + return FALSE; + for (i = 0; i < nent; i++) + pdefs[i].pixel = i; + fbdevGetColors(pScreen, nent, pdefs); + for (i = 0; i < nent; i++) { + pmap->red[i].co.local.red = pdefs[i].red; + pmap->red[i].co.local.green = pdefs[i].green; + pmap->red[i].co.local.blue = pdefs[i].blue; + } + free(pdefs); + return TRUE; + default: + return fbInitializeColormap(pmap); + } +} + +Bool +fbdevInitScreen(ScreenPtr pScreen) +{ + pScreen->CreateColormap = fbdevCreateColormap; + return TRUE; +} + +Bool +fbdevFinishInitScreen(ScreenPtr pScreen) +{ + if (!shadowSetup(pScreen)) + return FALSE; + +#ifdef RANDR + if (!fbdevRandRInit(pScreen)) + return FALSE; +#endif + + return TRUE; +} + +Bool +fbdevCreateResources(ScreenPtr pScreen) +{ + return fbdevSetShadow(pScreen); +} + +void +fbdevPreserve(KdCardInfo * card) +{ +} + +static int +fbdevUpdateFbColormap(FbdevPriv * priv, int minidx, int maxidx) +{ + struct fb_cmap cmap; + + cmap.start = minidx; + cmap.len = maxidx - minidx + 1; + cmap.red = &priv->red[minidx]; + cmap.green = &priv->green[minidx]; + cmap.blue = &priv->blue[minidx]; + cmap.transp = 0; + + return ioctl(priv->fd, FBIOPUTCMAP, &cmap); +} + +Bool +fbdevEnable(ScreenPtr pScreen) +{ + KdScreenPriv(pScreen); + FbdevPriv *priv = pScreenPriv->card->driver; + int k; + + priv->var.activate = FB_ACTIVATE_NOW | FB_CHANGE_CMAP_VBL; + + /* display it on the LCD */ + k = ioctl(priv->fd, FBIOPUT_VSCREENINFO, &priv->var); + if (k < 0) { + perror("FBIOPUT_VSCREENINFO"); + return FALSE; + } + + if (priv->fix.visual == FB_VISUAL_DIRECTCOLOR) { + int i; + + for (i = 0; + i < (1 << priv->var.red.length) || + i < (1 << priv->var.green.length) || + i < (1 << priv->var.blue.length); i++) { + priv->red[i] = i * 65535 / ((1 << priv->var.red.length) - 1); + priv->green[i] = i * 65535 / ((1 << priv->var.green.length) - 1); + priv->blue[i] = i * 65535 / ((1 << priv->var.blue.length) - 1); + } + + fbdevUpdateFbColormap(priv, 0, i); + } + return TRUE; +} + +Bool +fbdevDPMS(ScreenPtr pScreen, int mode) +{ + KdScreenPriv(pScreen); + FbdevPriv *priv = pScreenPriv->card->driver; + static int oldmode = -1; + + if (mode == oldmode) + return TRUE; +#ifdef FBIOPUT_POWERMODE + if (ioctl(priv->fd, FBIOPUT_POWERMODE, &mode) >= 0) { + oldmode = mode; + return TRUE; + } +#endif +#ifdef FBIOBLANK + if (ioctl(priv->fd, FBIOBLANK, mode ? mode + 1 : 0) >= 0) { + oldmode = mode; + return TRUE; + } +#endif + return FALSE; +} + +void +fbdevDisable(ScreenPtr pScreen) +{ +} + +void +fbdevRestore(KdCardInfo * card) +{ +} + +void +fbdevScreenFini(KdScreenInfo * screen) +{ +} + +void +fbdevCardFini(KdCardInfo * card) +{ + FbdevPriv *priv = card->driver; + + munmap(priv->fb_base, priv->fix.smem_len); + close(priv->fd); + free(priv); +} + +/* + * Retrieve actual colormap and return selected n entries in pdefs. + */ +void +fbdevGetColors(ScreenPtr pScreen, int n, xColorItem * pdefs) +{ + KdScreenPriv(pScreen); + FbdevPriv *priv = pScreenPriv->card->driver; + struct fb_cmap cmap; + int p; + int k; + int min, max; + + min = 256; + max = 0; + for (k = 0; k < n; k++) { + if (pdefs[k].pixel < min) + min = pdefs[k].pixel; + if (pdefs[k].pixel > max) + max = pdefs[k].pixel; + } + cmap.start = min; + cmap.len = max - min + 1; + cmap.red = &priv->red[min]; + cmap.green = &priv->green[min]; + cmap.blue = &priv->blue[min]; + cmap.transp = 0; + k = ioctl(priv->fd, FBIOGETCMAP, &cmap); + if (k < 0) { + perror("can't get colormap"); + return; + } + while (n--) { + p = pdefs->pixel; + pdefs->red = priv->red[p]; + pdefs->green = priv->green[p]; + pdefs->blue = priv->blue[p]; + pdefs++; + } +} + +/* + * Change colormap by updating n entries described in pdefs. + */ +void +fbdevPutColors(ScreenPtr pScreen, int n, xColorItem * pdefs) +{ + KdScreenPriv(pScreen); + FbdevPriv *priv = pScreenPriv->card->driver; + int p; + int min, max; + + min = 256; + max = 0; + while (n--) { + p = pdefs->pixel; + priv->red[p] = pdefs->red; + priv->green[p] = pdefs->green; + priv->blue[p] = pdefs->blue; + if (p < min) + min = p; + if (p > max) + max = p; + pdefs++; + } + + fbdevUpdateFbColormap(priv, min, max); +} diff --git a/hw/kdrive/fbdev/fbdev.h b/hw/kdrive/fbdev/fbdev.h new file mode 100644 index 0000000000..ffa5487741 --- /dev/null +++ b/hw/kdrive/fbdev/fbdev.h @@ -0,0 +1,84 @@ +/* + * Copyright © 1999 Keith Packard + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of Keith Packard not be used in + * advertising or publicity pertaining to distribution of the software without + * specific, written prior permission. Keith Packard makes no + * representations about the suitability of this software for any purpose. It + * is provided "as is" without express or implied warranty. + * + * KEITH PACKARD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL KEITH PACKARD BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _KDRIVE_FBDEV_H_ +#define _KDRIVE_FBDEV_H_ +#include +#include +#include +#include +#include "kdrive.h" + +#ifdef RANDR +#include "randrstr.h" +#endif + +typedef struct _fbdevPriv { + struct fb_var_screeninfo var; + struct fb_fix_screeninfo fix; + __u16 red[256]; + __u16 green[256]; + __u16 blue[256]; + int fd; + char *fb; + char *fb_base; +} FbdevPriv; + +typedef struct _fbdevScrPriv { + Rotation randr; + Bool shadow; +} FbdevScrPriv; + +extern KdCardFuncs fbdevFuncs; +extern const char *fbdevDevicePath; + +Bool fbdevCardInit(KdCardInfo * card); + +Bool fbdevScreenInit(KdScreenInfo * screen); + +Bool fbdevInitScreen(ScreenPtr pScreen); + +Bool fbdevFinishInitScreen(ScreenPtr pScreen); + +Bool fbdevCreateResources(ScreenPtr pScreen); + +void fbdevPreserve(KdCardInfo * card); + +Bool fbdevEnable(ScreenPtr pScreen); + +Bool fbdevDPMS(ScreenPtr pScreen, int mode); + +void fbdevDisable(ScreenPtr pScreen); + +void fbdevRestore(KdCardInfo * card); + +void fbdevScreenFini(KdScreenInfo * screen); + +void fbdevCardFini(KdCardInfo * card); + +void fbdevGetColors(ScreenPtr pScreen, int n, xColorItem * pdefs); + +void fbdevPutColors(ScreenPtr pScreen, int n, xColorItem * pdefs); + +Bool fbdevMapFramebuffer(KdScreenInfo * screen); + +#endif /* _KDRIVE_FBDEV_H_ */ diff --git a/hw/kdrive/fbdev/fbinit.c b/hw/kdrive/fbdev/fbinit.c new file mode 100644 index 0000000000..4956d323fa --- /dev/null +++ b/hw/kdrive/fbdev/fbinit.c @@ -0,0 +1,123 @@ +/* + * Copyright © 1999 Keith Packard + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of Keith Packard not be used in + * advertising or publicity pertaining to distribution of the software without + * specific, written prior permission. Keith Packard makes no + * representations about the suitability of this software for any purpose. It + * is provided "as is" without express or implied warranty. + * + * KEITH PACKARD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL KEITH PACKARD BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include "fbdev.h" +#include "miext/extinit_priv.h" +#include "os/cmdline.h" +#include "os/ddx_priv.h" + +void +InitCard(char *name) +{ + KdCardInfoAdd(&fbdevFuncs, 0); +} + +static const ExtensionModule ephyrExtensions[] = { +#ifdef GLXEXT + { GlxExtensionInit, "GLX", &noGlxExtension }, +#endif +}; + +static +void ephyrExtensionInit(void) +{ + LoadExtensionList(ephyrExtensions, ARRAY_SIZE(ephyrExtensions), TRUE); +} + +#if INPUTTHREAD +/** This function is called in Xserver/os/inputthread.c when starting + the input thread. */ +void +ddxInputThreadInit(void) +{ +} +#endif + +void +InitOutput(ScreenInfo * pScreenInfo, int argc, char **argv) +{ + if (serverGeneration == 1) + ephyrExtensionInit(); + + KdInitOutput(pScreenInfo, argc, argv); +} + +void +InitInput(int argc, char **argv) +{ + KdOsAddInputDrivers(); + KdInitInput(); +} + +void +CloseInput(void) +{ + KdCloseInput(); +} + +void +ddxUseMsg(void) +{ + KdUseMsg(); + ErrorF("\nXfbdev Device Usage:\n"); + ErrorF + ("-fb path Framebuffer device to use. Defaults to /dev/fb0\n"); + ErrorF("\n"); +} + +int +ddxProcessArgument(int argc, char **argv, int i) +{ + if (!strcmp(argv[i], "-fb")) { + if (i + 1 < argc) { + fbdevDevicePath = argv[i + 1]; + return 2; + } + UseMsg(); + exit(1); + } + + return KdProcessArgument(argc, argv, i); +} + +KdCardFuncs fbdevFuncs = { + .cardinit = fbdevCardInit, + .scrinit = fbdevScreenInit, + .initScreen = fbdevInitScreen, + .finishInitScreen = fbdevFinishInitScreen, + .createRes = fbdevCreateResources, + .preserve = fbdevPreserve, + .enable = fbdevEnable, + .dpms = fbdevDPMS, + .disable = fbdevDisable, + .restore = fbdevRestore, + .scrfini = fbdevScreenFini, + .cardfini = fbdevCardFini, + + /* no cursor or accel funcs */ + + .getColors = fbdevGetColors, + .putColors = fbdevPutColors, + + /* no closescreen func */ +}; diff --git a/hw/kdrive/fbdev/meson.build b/hw/kdrive/fbdev/meson.build new file mode 100644 index 0000000000..f015f699d4 --- /dev/null +++ b/hw/kdrive/fbdev/meson.build @@ -0,0 +1,35 @@ +srcs = [ + 'fbdev.c', + 'fbinit.c', + '../../stubs/ddxBeforeReset.c', +] + +xfbdev_server = executable( + 'Xfbdev', + srcs, + include_directories: [ + inc, + include_directories('../src'), + include_directories('../linux'), + ], + dependencies: common_dep, + link_with: [ + libxserver_main, + kdrive, + linux, + libxserver_fb, + libxserver, + libxserver_glx, + libxserver_config, + libxserver_xkb_stubs, + libglxvnd, + ], + install: true, +) + +xfbdev_man = configure_file( + input: 'Xfbdev.man', + output: 'Xfbdev.1', + configuration: manpage_config, +) +install_man(xfbdev_man) diff --git a/hw/kdrive/linux/evdev.c b/hw/kdrive/linux/evdev.c new file mode 100644 index 0000000000..eeb46d753b --- /dev/null +++ b/hw/kdrive/linux/evdev.c @@ -0,0 +1,526 @@ +/* + * Copyright © 2004 Keith Packard + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of Keith Packard not be used in + * advertising or publicity pertaining to distribution of the software without + * specific, written prior permission. Keith Packard makes no + * representations about the suitability of this software for any purpose. It + * is provided "as is" without express or implied warranty. + * + * KEITH PACKARD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL KEITH PACKARD BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include "inputstr.h" +#include "scrnintstr.h" +#include "kdrive.h" + +#define NUM_EVENTS 128 +#define ABS_UNSET -65535 + +#define BITS_PER_LONG (sizeof(long) * 8) +#define NBITS(x) ((((x)-1)/BITS_PER_LONG)+1) +#define ISBITSET(x,y) ((x)[LONG(y)] & BIT(y)) +#define OFF(x) ((x)%BITS_PER_LONG) +#define LONG(x) ((x)/BITS_PER_LONG) +#define BIT(x) (1 << OFF(x)) + +typedef struct _kevdev { + /* current device state */ + int rel[REL_MAX + 1]; + int abs[ABS_MAX + 1]; + int prevabs[ABS_MAX + 1]; + long key[NBITS(KEY_MAX + 1)]; + + /* supported device info */ + long relbits[NBITS(REL_MAX + 1)]; + long absbits[NBITS(ABS_MAX + 1)]; + long keybits[NBITS(KEY_MAX + 1)]; + struct input_absinfo absinfo[ABS_MAX + 1]; + int max_rel; + int max_abs; + + int fd; +} Kevdev; + +static void +EvdevPtrBtn(KdPointerInfo * pi, struct input_event *ev) +{ + int flags = KD_MOUSE_DELTA | pi->buttonState; + + if (ev->code >= BTN_MOUSE && ev->code < BTN_JOYSTICK) { + switch (ev->code) { + case BTN_LEFT: + if (ev->value == 1) + flags |= KD_BUTTON_1; + else + flags &= ~KD_BUTTON_1; + break; + case BTN_MIDDLE: + if (ev->value == 1) + flags |= KD_BUTTON_2; + else + flags &= ~KD_BUTTON_2; + break; + case BTN_RIGHT: + if (ev->value == 1) + flags |= KD_BUTTON_3; + else + flags &= ~KD_BUTTON_3; + break; + default: + /* Unknow button */ + break; + } + + KdEnqueuePointerEvent(pi, flags, 0, 0, 0); + } +} + +static void +EvdevPtrMotion(KdPointerInfo * pi, struct input_event *ev) +{ + Kevdev *ke = pi->driverPrivate; + int i; + int flags = KD_MOUSE_DELTA | pi->buttonState; + + for (i = 0; i <= ke->max_rel; i++) + if (ke->rel[i]) { + int a; + + for (a = 0; a <= ke->max_rel; a++) { + if (ISBITSET(ke->relbits, a)) { + if (a == 0) + KdEnqueuePointerEvent(pi, flags, ke->rel[a], 0, 0); + else if (a == 1) + KdEnqueuePointerEvent(pi, flags, 0, ke->rel[a], 0); + } + ke->rel[a] = 0; + } + break; + } + for (i = 0; i < ke->max_abs; i++) + if (ke->abs[i] != ke->prevabs[i]) { + int a; + + ErrorF("abs"); + for (a = 0; a <= ke->max_abs; a++) { + if (ISBITSET(ke->absbits, a)) + ErrorF(" %d=%d", a, ke->abs[a]); + ke->prevabs[a] = ke->abs[a]; + } + ErrorF("\n"); + break; + } + + if (ev->code == REL_WHEEL) { + for (i = 0; i < abs(ev->value); i++) { + if (ev->value > 0) + flags |= KD_BUTTON_4; + else + flags |= KD_BUTTON_5; + + KdEnqueuePointerEvent(pi, flags, 0, 0, 0); + + if (ev->value > 0) + flags &= ~KD_BUTTON_4; + else + flags &= ~KD_BUTTON_5; + + KdEnqueuePointerEvent(pi, flags, 0, 0, 0); + } + } + +} + +static void +EvdevPtrRead(int evdevPort, void *closure) +{ + KdPointerInfo *pi = closure; + Kevdev *ke = pi->driverPrivate; + int i; + struct input_event events[NUM_EVENTS]; + int n; + + n = read(evdevPort, &events, NUM_EVENTS * sizeof(struct input_event)); + if (n <= 0) { + if (errno == ENODEV) + DeleteInputDeviceRequest(pi->dixdev); + return; + } + + n /= sizeof(struct input_event); + for (i = 0; i < n; i++) { + switch (events[i].type) { + case EV_SYN: + break; + case EV_KEY: + EvdevPtrBtn(pi, &events[i]); + break; + case EV_REL: + ke->rel[events[i].code] += events[i].value; + EvdevPtrMotion(pi, &events[i]); + break; + case EV_ABS: + ke->abs[events[i].code] = events[i].value; + EvdevPtrMotion(pi, &events[i]); + break; + } + } +} + +const char *kdefaultEvdev[] = { + "/dev/input/event0", + "/dev/input/event1", + "/dev/input/event2", + "/dev/input/event3", +}; + +#define NUM_DEFAULT_EVDEV (sizeof (kdefaultEvdev) / sizeof (kdefaultEvdev[0])) + +static Status +EvdevPtrInit(KdPointerInfo * pi) +{ + int i; + int fd; + + if (!pi->path) { + for (i = 0; i < NUM_DEFAULT_EVDEV; i++) { + fd = open(kdefaultEvdev[i], 2); + if (fd >= 0) { + pi->path = strdup(kdefaultEvdev[i]); + break; + } + } + } + else { + fd = open(pi->path, O_RDWR); + if (fd < 0) { + ErrorF("Failed to open evdev device %s\n", pi->path); + return BadMatch; + } + } + + close(fd); + + if (!pi->name) + pi->name = strdup("Evdev mouse"); + + return Success; +} + +static Status +EvdevPtrEnable(KdPointerInfo * pi) +{ + int fd; + unsigned long ev[NBITS(EV_MAX)]; + Kevdev *ke; + + if (!pi || !pi->path) + return BadImplementation; + + fd = open(pi->path, 2); + if (fd < 0) + return BadMatch; + + if (ioctl(fd, EVIOCGRAB, 1) < 0) + perror("Grabbing evdev mouse device failed"); + + if (ioctl(fd, EVIOCGBIT(0 /*EV*/, sizeof(ev)), ev) < 0) { + perror("EVIOCGBIT 0"); + close(fd); + return BadMatch; + } + ke = calloc(1, sizeof(Kevdev)); + if (!ke) { + close(fd); + return BadAlloc; + } + if (ISBITSET(ev, EV_KEY)) { + if (ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(ke->keybits)), ke->keybits) < 0) { + perror("EVIOCGBIT EV_KEY"); + free(ke); + close(fd); + return BadMatch; + } + } + if (ISBITSET(ev, EV_REL)) { + if (ioctl(fd, EVIOCGBIT(EV_REL, sizeof(ke->relbits)), ke->relbits) < 0) { + perror("EVIOCGBIT EV_REL"); + free(ke); + close(fd); + return BadMatch; + } + for (ke->max_rel = REL_MAX; ke->max_rel >= 0; ke->max_rel--) + if (ISBITSET(ke->relbits, ke->max_rel)) + break; + } + if (ISBITSET(ev, EV_ABS)) { + int i; + + if (ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(ke->absbits)), ke->absbits) < 0) { + perror("EVIOCGBIT EV_ABS"); + free(ke); + close(fd); + return BadMatch; + } + for (ke->max_abs = ABS_MAX; ke->max_abs >= 0; ke->max_abs--) + if (ISBITSET(ke->absbits, ke->max_abs)) + break; + for (i = 0; i <= ke->max_abs; i++) { + if (ISBITSET(ke->absbits, i)) + if (ioctl(fd, EVIOCGABS(i), &ke->absinfo[i]) < 0) { + perror("EVIOCGABS"); + break; + } + ke->prevabs[i] = ABS_UNSET; + } + if (i <= ke->max_abs) { + free(ke); + close(fd); + return BadValue; + } + } + if (!KdRegisterFd(fd, EvdevPtrRead, pi)) { + free(ke); + close(fd); + return BadAlloc; + } + pi->driverPrivate = ke; + ke->fd = fd; + + return Success; +} + +static void +EvdevPtrDisable(KdPointerInfo * pi) +{ + Kevdev *ke; + + ke = pi->driverPrivate; + + if (!pi || !pi->driverPrivate) + return; + + KdUnregisterFd(pi, ke->fd, TRUE); + + if (ioctl(ke->fd, EVIOCGRAB, 0) < 0) + perror("Ungrabbing evdev mouse device failed"); + + free(ke); + pi->driverPrivate = 0; +} + +static void +EvdevPtrFini(KdPointerInfo * pi) +{ +} + +/* + * Evdev keyboard functions + */ + +static void +readMapping(KdKeyboardInfo * ki) +{ + if (!ki) + return; + + ki->minScanCode = 0; + ki->maxScanCode = 247; +} + +static void +EvdevKbdRead(int evdevPort, void *closure) +{ + KdKeyboardInfo *ki = closure; + struct input_event events[NUM_EVENTS]; + int i, n; + + n = read(evdevPort, &events, NUM_EVENTS * sizeof(struct input_event)); + if (n <= 0) { + if (errno == ENODEV) + DeleteInputDeviceRequest(ki->dixdev); + return; + } + + n /= sizeof(struct input_event); + for (i = 0; i < n; i++) { + if (events[i].type == EV_KEY) + KdEnqueueKeyboardEvent(ki, events[i].code, !events[i].value); +/* FIXME: must implement other types of events + else + ErrorF("Event type (%d) not delivered\n", events[i].type); +*/ + } +} + +static Status +EvdevKbdInit(KdKeyboardInfo * ki) +{ + int fd; + + if (!ki->path) { + ErrorF("Couldn't find evdev device path\n"); + return BadValue; + } + else { + fd = open(ki->path, O_RDWR); + if (fd < 0) { + ErrorF("Failed to open evdev device %s\n", ki->path); + return BadMatch; + } + } + + close(fd); + + if (!ki->name) + ki->name = strdup("Evdev keyboard"); + + readMapping(ki); + + return Success; +} + +static Status +EvdevKbdEnable(KdKeyboardInfo * ki) +{ + unsigned long ev[NBITS(EV_MAX)]; + Kevdev *ke; + int fd; + + if (!ki || !ki->path) + return BadImplementation; + + fd = open(ki->path, O_RDWR); + if (fd < 0) + return BadMatch; + + if (ioctl(fd, EVIOCGRAB, 1) < 0) + perror("Grabbing evdev keyboard device failed"); + + if (ioctl(fd, EVIOCGBIT(0 /*EV*/, sizeof(ev)), ev) < 0) { + perror("EVIOCGBIT 0"); + close(fd); + return BadMatch; + } + + ke = calloc(1, sizeof(Kevdev)); + if (!ke) { + close(fd); + return BadAlloc; + } + + if (!KdRegisterFd(fd, EvdevKbdRead, ki)) { + free(ke); + close(fd); + return BadAlloc; + } + ki->driverPrivate = ke; + ke->fd = fd; + + return Success; +} + +static void +EvdevKbdLeds(KdKeyboardInfo * ki, int leds) +{ + struct input_event event; + Kevdev *ke; + int i; + + if (!ki) + return; + + ke = ki->driverPrivate; + + if (!ke) + return; + + memset(&event, 0, sizeof(event)); + + event.type = EV_LED; + event.code = LED_CAPSL; + event.value = leds & (1 << 0) ? 1 : 0; + i = write(ke->fd, (char *) &event, sizeof(event)); + (void) i; + + event.type = EV_LED; + event.code = LED_NUML; + event.value = leds & (1 << 1) ? 1 : 0; + i = write(ke->fd, (char *) &event, sizeof(event)); + (void) i; + + event.type = EV_LED; + event.code = LED_SCROLLL; + event.value = leds & (1 << 2) ? 1 : 0; + i = write(ke->fd, (char *) &event, sizeof(event)); + (void) i; + + event.type = EV_LED; + event.code = LED_COMPOSE; + event.value = leds & (1 << 3) ? 1 : 0; + i = write(ke->fd, (char *) &event, sizeof(event)); + (void) i; +} + +static void +EvdevKbdBell(KdKeyboardInfo * ki, int volume, int frequency, int duration) +{ +} + +static void +EvdevKbdDisable(KdKeyboardInfo * ki) +{ + Kevdev *ke; + + ke = ki->driverPrivate; + + if (!ki || !ki->driverPrivate) + return; + + KdUnregisterFd(ki, ke->fd, TRUE); + + if (ioctl(ke->fd, EVIOCGRAB, 0) < 0) + perror("Ungrabbing evdev keyboard device failed"); + + free(ke); + ki->driverPrivate = 0; +} + +static void +EvdevKbdFini(KdKeyboardInfo * ki) +{ +} + +KdPointerDriver LinuxEvdevMouseDriver = { + .name = "evdev", + .Init = EvdevPtrInit, + .Enable = EvdevPtrEnable, + .Disable = EvdevPtrDisable, + .Fini = EvdevPtrFini, +}; + +KdKeyboardDriver LinuxEvdevKeyboardDriver = { + .name = "evdev", + .Init = EvdevKbdInit, + .Enable = EvdevKbdEnable, + .Leds = EvdevKbdLeds, + .Bell = EvdevKbdBell, + .Disable = EvdevKbdDisable, + .Fini = EvdevKbdFini, +}; diff --git a/hw/kdrive/linux/keyboard.c b/hw/kdrive/linux/keyboard.c new file mode 100644 index 0000000000..83b2ffb589 --- /dev/null +++ b/hw/kdrive/linux/keyboard.c @@ -0,0 +1,349 @@ +/* + * Copyright © 1999 Keith Packard + * XKB integration © 2006 Nokia Corporation, author: Tomas Frydrych + * + * LinuxKeyboardRead() XKB code based on xf86KbdLnx.c: + * Copyright © 1990,91 by Thomas Roell, Dinkelscherben, Germany. + * Copyright © 1994-2001 by The XFree86 Project, Inc. + * + * 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 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 COPYRIGHT HOLDER(S) OR AUTHOR(S) 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. + * + * Except as contained in this notice, the name of the copyright holder(s) + * and author(s) shall not be used in advertising or otherwise to promote + * the sale, use or other dealings in this Software without prior written + * authorization from the copyright holder(s) and author(s). + */ + +#include +#include "kdrive.h" +#include +#include +#define XK_PUBLISHING +#include +#include +#include + +extern int LinuxConsoleFd; + +/* + * We need these to handle extended scancodes correctly (I could just use the + * numbers below, but this makes the code more readable + */ + +/* The prefix codes */ +#define KEY_Prefix0 /* special 0x60 */ 96 +#define KEY_Prefix1 /* special 0x61 */ 97 + +/* The raw scancodes */ +#define KEY_Enter /* Enter 0x1c */ 28 +#define KEY_LCtrl /* Ctrl(left) 0x1d */ 29 +#define KEY_Slash /* / (Slash) ? 0x35 */ 53 +#define KEY_KP_Multiply /* * 0x37 */ 55 +#define KEY_Alt /* Alt(left) 0x38 */ 56 +#define KEY_F3 /* F3 0x3d */ 61 +#define KEY_F4 /* F4 0x3e */ 62 +#define KEY_F5 /* F5 0x3f */ 63 +#define KEY_F6 /* F6 0x40 */ 64 +#define KEY_F7 /* F7 0x41 */ 65 +#define KEY_ScrollLock /* ScrollLock 0x46 */ 70 +#define KEY_KP_7 /* 7 Home 0x47 */ 71 +#define KEY_KP_8 /* 8 Up 0x48 */ 72 +#define KEY_KP_9 /* 9 PgUp 0x49 */ 73 +#define KEY_KP_Minus /* - (Minus) 0x4a */ 74 +#define KEY_KP_4 /* 4 Left 0x4b */ 75 +#define KEY_KP_5 /* 5 0x4c */ 76 +#define KEY_KP_6 /* 6 Right 0x4d */ 77 +#define KEY_KP_Plus /* + (Plus) 0x4e */ 78 +#define KEY_KP_1 /* 1 End 0x4f */ 79 +#define KEY_KP_2 /* 2 Down 0x50 */ 80 +#define KEY_KP_3 /* 3 PgDown 0x51 */ 81 +#define KEY_KP_0 /* 0 Insert 0x52 */ 82 +#define KEY_KP_Decimal /* . (Decimal) Delete 0x53 */ 83 +#define KEY_Home /* Home 0x59 */ 89 +#define KEY_Up /* Up 0x5a */ 90 +#define KEY_PgUp /* PgUp 0x5b */ 91 +#define KEY_Left /* Left 0x5c */ 92 +#define KEY_Begin /* Begin 0x5d */ 93 +#define KEY_Right /* Right 0x5e */ 94 +#define KEY_End /* End 0x5f */ 95 +#define KEY_Down /* Down 0x60 */ 96 +#define KEY_PgDown /* PgDown 0x61 */ 97 +#define KEY_Insert /* Insert 0x62 */ 98 +#define KEY_Delete /* Delete 0x63 */ 99 +#define KEY_KP_Enter /* Enter 0x64 */ 100 +#define KEY_RCtrl /* Ctrl(right) 0x65 */ 101 +#define KEY_Pause /* Pause 0x66 */ 102 +#define KEY_Print /* Print 0x67 */ 103 +#define KEY_KP_Divide /* Divide 0x68 */ 104 +#define KEY_AltLang /* AtlLang(right) 0x69 */ 105 +#define KEY_Break /* Break 0x6a */ 106 +#define KEY_LMeta /* Left Meta 0x6b */ 107 +#define KEY_RMeta /* Right Meta 0x6c */ 108 +#define KEY_Menu /* Menu 0x6d */ 109 +#define KEY_F13 /* F13 0x6e */ 110 +#define KEY_F14 /* F14 0x6f */ 111 +#define KEY_F15 /* F15 0x70 */ 112 +#define KEY_F16 /* F16 0x71 */ 113 +#define KEY_F17 /* F17 0x72 */ 114 +#define KEY_KP_DEC /* KP_DEC 0x73 */ 115 + +static void +LinuxKeyboardRead(int fd, void *closure) +{ + unsigned char buf[256], *b; + int n; + unsigned char prefix = 0, scancode = 0; + + while ((n = read(fd, buf, sizeof(buf))) > 0) { + b = buf; + while (n--) { + /* + * With xkb we use RAW mode for reading the console, which allows us + * process extended scancodes. + * + * See if this is a prefix extending the following keycode + */ + if (!prefix && ((b[0] & 0x7f) == KEY_Prefix0)) { + prefix = KEY_Prefix0; + /* swallow this up */ + b++; + continue; + } + else if (!prefix && ((b[0] & 0x7f) == KEY_Prefix1)) { + prefix = KEY_Prefix1; + /* swallow this up */ + b++; + continue; + } + scancode = b[0] & 0x7f; + + switch (prefix) { + /* from xf86Events.c */ + case KEY_Prefix0: + { + switch (scancode) { + case KEY_KP_7: + scancode = KEY_Home; + break; /* curs home */ + case KEY_KP_8: + scancode = KEY_Up; + break; /* curs up */ + case KEY_KP_9: + scancode = KEY_PgUp; + break; /* curs pgup */ + case KEY_KP_4: + scancode = KEY_Left; + break; /* curs left */ + case KEY_KP_5: + scancode = KEY_Begin; + break; /* curs begin */ + case KEY_KP_6: + scancode = KEY_Right; + break; /* curs right */ + case KEY_KP_1: + scancode = KEY_End; + break; /* curs end */ + case KEY_KP_2: + scancode = KEY_Down; + break; /* curs down */ + case KEY_KP_3: + scancode = KEY_PgDown; + break; /* curs pgdown */ + case KEY_KP_0: + scancode = KEY_Insert; + break; /* curs insert */ + case KEY_KP_Decimal: + scancode = KEY_Delete; + break; /* curs delete */ + case KEY_Enter: + scancode = KEY_KP_Enter; + break; /* keypad enter */ + case KEY_LCtrl: + scancode = KEY_RCtrl; + break; /* right ctrl */ + case KEY_KP_Multiply: + scancode = KEY_Print; + break; /* print */ + case KEY_Slash: + scancode = KEY_KP_Divide; + break; /* keyp divide */ + case KEY_Alt: + scancode = KEY_AltLang; + break; /* right alt */ + case KEY_ScrollLock: + scancode = KEY_Break; + break; /* curs break */ + case 0x5b: + scancode = KEY_LMeta; + break; + case 0x5c: + scancode = KEY_RMeta; + break; + case 0x5d: + scancode = KEY_Menu; + break; + case KEY_F3: + scancode = KEY_F13; + break; + case KEY_F4: + scancode = KEY_F14; + break; + case KEY_F5: + scancode = KEY_F15; + break; + case KEY_F6: + scancode = KEY_F16; + break; + case KEY_F7: + scancode = KEY_F17; + break; + case KEY_KP_Plus: + scancode = KEY_KP_DEC; + break; + /* Ignore virtual shifts (E0 2A, E0 AA, E0 36, E0 B6) */ + case 0x2A: + case 0x36: + b++; + prefix = 0; + continue; + default: + /* + * "Internet" keyboards are generating lots of new + * codes. Let them pass. There is little consistency + * between them, so don't bother with symbolic names at + * this level. + */ + scancode += 0x78; + } + break; + } + + case KEY_Prefix1: + { + /* we do no handle these */ + b++; + prefix = 0; + continue; + } + + default: /* should not happen */ + case 0: /* do nothing */ + ; + } + + prefix = 0; + KdEnqueueKeyboardEvent(closure, scancode, b[0] & 0x80); + b++; + } + } +} + +static int LinuxKbdTrans; +static struct termios LinuxTermios; + +static Status +LinuxKeyboardEnable(KdKeyboardInfo * ki) +{ + struct termios nTty; + unsigned char buf[256]; + int n; + int fd; + + if (!ki) + return !Success; + + fd = LinuxConsoleFd; + ki->driverPrivate = (void *) (intptr_t) fd; + + ioctl(fd, KDGKBMODE, &LinuxKbdTrans); + tcgetattr(fd, &LinuxTermios); + ioctl(fd, KDSKBMODE, K_RAW); + nTty = LinuxTermios; + nTty.c_iflag = (IGNPAR | IGNBRK) & (~PARMRK) & (~ISTRIP); + nTty.c_oflag = 0; + nTty.c_cflag = CREAD | CS8; + nTty.c_lflag = 0; + nTty.c_cc[VTIME] = 0; + nTty.c_cc[VMIN] = 1; + cfsetispeed(&nTty, 9600); + cfsetospeed(&nTty, 9600); + tcsetattr(fd, TCSANOW, &nTty); + /* + * Flush any pending keystrokes + */ + while ((n = read(fd, buf, sizeof(buf))) > 0); + KdRegisterFd(fd, LinuxKeyboardRead, ki); + return Success; +} + +static void +LinuxKeyboardDisable(KdKeyboardInfo * ki) +{ + int fd; + + if (!ki) + return; + + fd = (int) (intptr_t) ki->driverPrivate; + + KdUnregisterFd(ki, fd, FALSE); + ioctl(fd, KDSKBMODE, LinuxKbdTrans); + tcsetattr(fd, TCSANOW, &LinuxTermios); +} + +static Status +LinuxKeyboardPreInit(KdKeyboardInfo * ki) +{ + ki->xkbRules = strdup("base"); + return Success; +} + +static Status +LinuxKeyboardInit(KdKeyboardInfo * ki) +{ + if (!ki) + return !Success; + + free(ki->path); + ki->path = strdup("console"); + free(ki->name); + ki->name = strdup("Linux console keyboard"); + + ki->maxScanCode = 0; + ki->maxScanCode = 255; + return Success; +} + +static void +LinuxKeyboardLeds(KdKeyboardInfo * ki, int leds) +{ + if (!ki) + return; + + ioctl((int) (intptr_t) ki->driverPrivate, KDSETLED, leds & 7); +} + +KdKeyboardDriver LinuxKeyboardDriver = { + .name = "keyboard", + .PreInit = LinuxKeyboardPreInit, + .Init = LinuxKeyboardInit, + .Enable = LinuxKeyboardEnable, + .Leds = LinuxKeyboardLeds, + .Disable = LinuxKeyboardDisable, +}; diff --git a/hw/kdrive/linux/linux.c b/hw/kdrive/linux/linux.c new file mode 100644 index 0000000000..6422688bed --- /dev/null +++ b/hw/kdrive/linux/linux.c @@ -0,0 +1,362 @@ +/* + * Copyright © 1999 Keith Packard + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of Keith Packard not be used in + * advertising or publicity pertaining to distribution of the software without + * specific, written prior permission. Keith Packard makes no + * representations about the suitability of this software for any purpose. It + * is provided "as is" without express or implied warranty. + * + * KEITH PACKARD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL KEITH PACKARD BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include "kdrive.h" +#include "os/osdep.h" +#include +#include +#include +#include +#include +#include +#include + +#ifdef KDRIVE_MOUSE +extern KdPointerDriver LinuxMouseDriver; +extern KdPointerDriver Ps2MouseDriver; +extern KdPointerDriver MsMouseDriver; +#endif +#ifdef KDRIVE_TSLIB +extern KdPointerDriver TsDriver; +#endif +#ifdef KDRIVE_EVDEV +extern KdPointerDriver LinuxEvdevMouseDriver; +extern KdKeyboardDriver LinuxEvdevKeyboardDriver; +#endif +#ifdef KDRIVE_KBD +extern KdKeyboardDriver LinuxKeyboardDriver; +#endif + +static int vtno; +int LinuxConsoleFd; +int LinuxApmFd = -1; +static int activeVT; +static Bool enabled; + +static void +LinuxVTRequest(int sig) +{ + kdSwitchPending = TRUE; +} + +/* Check before chowning -- this avoids touching the file system */ +static void +LinuxCheckChown(const char *file) +{ + struct stat st; + __uid_t u; + __gid_t g; + int r; + + if (stat(file, &st) < 0) + return; + u = getuid(); + g = getgid(); + if (st.st_uid != u || st.st_gid != g) { + r = chown(file, u, g); + (void) r; + } +} + +static int +LinuxInit(void) +{ + int fd = -1; + char vtname[11]; + struct vt_stat vts; + + LinuxConsoleFd = -1; + /* check if we're run with euid==0 */ + if (geteuid() != 0) { + FatalError("LinuxInit: Server must be suid root\n"); + } + + if (kdVirtualTerminal >= 0) + vtno = kdVirtualTerminal; + else { + if ((fd = open("/dev/tty0", O_WRONLY, 0)) < 0) { + FatalError("LinuxInit: Cannot open /dev/tty0 (%s)\n", + strerror(errno)); + } + if ((ioctl(fd, VT_OPENQRY, &vtno) < 0) || (vtno == -1)) { + FatalError("xf86OpenConsole: Cannot find a free VT\n"); + } + close(fd); + } + + snprintf(vtname, sizeof(vtname), "/dev/tty%d", vtno); /* /dev/tty1-64 */ + + if ((LinuxConsoleFd = open(vtname, O_RDWR | O_NDELAY, 0)) < 0) { + FatalError("LinuxInit: Cannot open %s (%s)\n", vtname, strerror(errno)); + } + + /* change ownership of the vt */ + LinuxCheckChown(vtname); + + /* + * the current VT device we're running on is not "console", we want + * to grab all consoles too + * + * Why is this needed? + */ + LinuxCheckChown("/dev/tty0"); + /* + * Linux doesn't switch to an active vt after the last close of a vt, + * so we do this ourselves by remembering which is active now. + */ + memset(&vts, '\0', sizeof(vts)); /* valgrind */ + if (ioctl(LinuxConsoleFd, VT_GETSTATE, &vts) == 0) { + activeVT = vts.v_active; + } + + return 1; +} + +static void +LinuxSetSwitchMode(int mode) +{ + struct sigaction act; + struct vt_mode VT; + + if (ioctl(LinuxConsoleFd, VT_GETMODE, &VT) < 0) { + FatalError("LinuxInit: VT_GETMODE failed\n"); + } + + if (mode == VT_PROCESS) { + act.sa_handler = LinuxVTRequest; + sigemptyset(&act.sa_mask); + act.sa_flags = 0; + sigaction(SIGUSR1, &act, 0); + + VT.mode = mode; + VT.relsig = SIGUSR1; + VT.acqsig = SIGUSR1; + } + else { + act.sa_handler = SIG_IGN; + sigemptyset(&act.sa_mask); + act.sa_flags = 0; + sigaction(SIGUSR1, &act, 0); + + VT.mode = mode; + VT.relsig = 0; + VT.acqsig = 0; + } + if (ioctl(LinuxConsoleFd, VT_SETMODE, &VT) < 0) { + FatalError("LinuxInit: VT_SETMODE failed\n"); + } +} + +static Bool LinuxApmRunning; + +static void +LinuxApmNotify(int fd, int mask, void *blockData) +{ + apm_event_t event; + Bool running = LinuxApmRunning; + int cmd = APM_IOC_SUSPEND; + + while (read(fd, &event, sizeof(event)) == sizeof(event)) { + switch (event) { + case APM_SYS_STANDBY: + case APM_USER_STANDBY: + running = FALSE; + cmd = APM_IOC_STANDBY; + break; + case APM_SYS_SUSPEND: + case APM_USER_SUSPEND: + case APM_CRITICAL_SUSPEND: + running = FALSE; + cmd = APM_IOC_SUSPEND; + break; + case APM_NORMAL_RESUME: + case APM_CRITICAL_RESUME: + case APM_STANDBY_RESUME: + running = TRUE; + break; + } + } + if (running && !LinuxApmRunning) { + KdResume(); + LinuxApmRunning = TRUE; + } + else if (!running && LinuxApmRunning) { + KdSuspend(); + LinuxApmRunning = FALSE; + ioctl(fd, cmd, 0); + } +} + +#ifdef FNONBLOCK +#define NOBLOCK FNONBLOCK +#else +#define NOBLOCK FNDELAY +#endif + +static void +LinuxEnable(void) +{ + if (enabled) + return; + if (kdSwitchPending) { + kdSwitchPending = FALSE; + ioctl(LinuxConsoleFd, VT_RELDISP, VT_ACKACQ); + } + /* + * Open the APM driver + */ + LinuxApmFd = open("/dev/apm_bios", 2); + if (LinuxApmFd < 0 && errno == ENOENT) + LinuxApmFd = open("/dev/misc/apm_bios", 2); + if (LinuxApmFd >= 0) { + LinuxApmRunning = TRUE; + fcntl(LinuxApmFd, F_SETFL, fcntl(LinuxApmFd, F_GETFL) | NOBLOCK); + SetNotifyFd(LinuxApmFd, LinuxApmNotify, X_NOTIFY_READ, NULL); + } + + /* + * now get the VT + */ + LinuxSetSwitchMode(VT_AUTO); + if (ioctl(LinuxConsoleFd, VT_ACTIVATE, vtno) != 0) { + FatalError("LinuxInit: VT_ACTIVATE failed\n"); + } + if (ioctl(LinuxConsoleFd, VT_WAITACTIVE, vtno) != 0) { + FatalError("LinuxInit: VT_WAITACTIVE failed\n"); + } + LinuxSetSwitchMode(VT_PROCESS); + if (ioctl(LinuxConsoleFd, KDSETMODE, KD_GRAPHICS) < 0) { + FatalError("LinuxInit: KDSETMODE KD_GRAPHICS failed\n"); + } + enabled = TRUE; +} + +static void +LinuxDisable(void) +{ + ioctl(LinuxConsoleFd, KDSETMODE, KD_TEXT); /* Back to text mode ... */ + if (kdSwitchPending) { + kdSwitchPending = FALSE; + ioctl(LinuxConsoleFd, VT_RELDISP, 1); + } + enabled = FALSE; + if (LinuxApmFd >= 0) { + RemoveNotifyFd(LinuxApmFd); + close(LinuxApmFd); + LinuxApmFd = -1; + } +} + +static void +LinuxFini(void) +{ + struct vt_mode VT; + struct vt_stat vts; + int fd; + + if (LinuxConsoleFd < 0) + return; + + if (ioctl(LinuxConsoleFd, VT_GETMODE, &VT) != -1) { + VT.mode = VT_AUTO; + ioctl(LinuxConsoleFd, VT_SETMODE, &VT); /* set dflt vt handling */ + } + memset(&vts, '\0', sizeof(vts)); /* valgrind */ + ioctl(LinuxConsoleFd, VT_GETSTATE, &vts); + if (vtno == vts.v_active) { + /* + * Find a legal VT to switch to, either the one we started from + * or the lowest active one that isn't ours + */ + if (activeVT < 0 || + activeVT == vts.v_active || !(vts.v_state & (1 << activeVT))) { + for (activeVT = 1; activeVT < 16; activeVT++) + if (activeVT != vtno && (vts.v_state & (1 << activeVT))) + break; + if (activeVT == 16) + activeVT = -1; + } + /* + * Perform a switch back to the active VT when we were started + */ + if (activeVT >= -1) { + ioctl(LinuxConsoleFd, VT_ACTIVATE, activeVT); + ioctl(LinuxConsoleFd, VT_WAITACTIVE, activeVT); + activeVT = -1; + } + } + close(LinuxConsoleFd); /* make the vt-manager happy */ + LinuxConsoleFd = -1; + fd = open("/dev/tty0", O_RDWR | O_NDELAY, 0); + if (fd >= 0) { + memset(&vts, '\0', sizeof(vts)); /* valgrind */ + ioctl(fd, VT_GETSTATE, &vts); + if (ioctl(fd, VT_DISALLOCATE, vtno) < 0) + fprintf(stderr, "Can't deallocate console %d %s\n", vtno, + strerror(errno)); + close(fd); + } + return; +} + +void +KdOsAddInputDrivers(void) +{ +#ifdef KDRIVE_MOUSE + KdAddPointerDriver(&LinuxMouseDriver); + KdAddPointerDriver(&MsMouseDriver); + KdAddPointerDriver(&Ps2MouseDriver); +#endif +#ifdef KDRIVE_TSLIB + KdAddPointerDriver(&TsDriver); +#endif +#ifdef KDRIVE_EVDEV + KdAddPointerDriver(&LinuxEvdevMouseDriver); + KdAddKeyboardDriver(&LinuxEvdevKeyboardDriver); +#endif +#ifdef KDRIVE_KBD + KdAddKeyboardDriver(&LinuxKeyboardDriver); +#endif +} + +static void +LinuxBell(int volume, int pitch, int duration) +{ + if (volume && pitch) + ioctl(LinuxConsoleFd, KDMKTONE, ((1193190 / pitch) & 0xffff) | + (((unsigned long) duration * volume / 50) << 16)); +} + +KdOsFuncs LinuxFuncs = { + .Init = LinuxInit, + .Enable = LinuxEnable, + .Disable = LinuxDisable, + .Fini = LinuxFini, + .Bell = LinuxBell, +}; + +void +OsVendorInit(void) +{ + KdOsInit(&LinuxFuncs); +} diff --git a/hw/kdrive/linux/meson.build b/hw/kdrive/linux/meson.build new file mode 100644 index 0000000000..53085036c1 --- /dev/null +++ b/hw/kdrive/linux/meson.build @@ -0,0 +1,33 @@ +srcs_linux = [ + 'linux.c' +] + +kdrive_linux_dep = common_dep + +if build_kdrive_kbd + srcs_linux += 'keyboard.c' +endif + +if build_kdrive_mouse + srcs_linux += 'mouse.c' + srcs_linux += 'ms.c' + srcs_linux += 'ps2.c' +endif + +if build_kdrive_evdev + srcs_linux += 'evdev.c' +endif + +if build_kdrive_tslib + srcs_linux += 'tslib.c' + kdrive_linux_dep += tslib_dep +endif + +linux = static_library('linux', + srcs_linux, + include_directories: [ + inc, + include_directories('../src'), + ], + dependencies: kdrive_linux_dep, +) diff --git a/hw/kdrive/linux/mouse.c b/hw/kdrive/linux/mouse.c new file mode 100644 index 0000000000..efea379e06 --- /dev/null +++ b/hw/kdrive/linux/mouse.c @@ -0,0 +1,978 @@ +/* + * Copyright © 2001 Keith Packard + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of Keith Packard not be used in + * advertising or publicity pertaining to distribution of the software without + * specific, written prior permission. Keith Packard makes no + * representations about the suitability of this software for any purpose. It + * is provided "as is" without express or implied warranty. + * + * KEITH PACKARD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL KEITH PACKARD BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include "os/xserver_poll.h" +#include "inputstr.h" +#include "scrnintstr.h" +#include "kdrive.h" + +#undef DEBUG +#undef DEBUG_BYTES +#define KBUFIO_SIZE 256 +#define MOUSE_TIMEOUT 100 + +typedef struct _kbufio { + int fd; + unsigned char buf[KBUFIO_SIZE]; + int avail; + int used; +} Kbufio; + +static Bool +MouseWaitForReadable(int fd, int timeout) +{ + struct pollfd poll_fd; + int n; + CARD32 done; + + done = GetTimeInMillis() + timeout; + poll_fd.fd = fd; + poll_fd.events = POLLIN; + for (;;) { + n = xserver_poll(&poll_fd, 1, timeout); + if (n > 0) + return TRUE; + if (n < 0 && (errno == EAGAIN || errno == EINTR)) { + timeout = (int) (done - GetTimeInMillis()); + if (timeout > 0) + continue; + } + break; + } + return FALSE; +} + +static int +MouseReadByte(Kbufio * b, int timeout) +{ + int n; + + if (b->avail <= b->used) { + if (timeout && !MouseWaitForReadable(b->fd, timeout)) { +#ifdef DEBUG_BYTES + ErrorF("\tTimeout %d\n", timeout); +#endif + return -1; + } + n = read(b->fd, b->buf, KBUFIO_SIZE); + if (n <= 0) + return -1; + b->avail = n; + b->used = 0; + } +#ifdef DEBUG_BYTES + ErrorF("\tget %02x\n", b->buf[b->used]); +#endif + return b->buf[b->used++]; +} + +#if NOTUSED +static int +MouseFlush(Kbufio * b, char *buf, int size) +{ + CARD32 now = GetTimeInMillis(); + CARD32 done = now + 100; + int c; + int n = 0; + + while ((c = MouseReadByte(b, done - now)) != -1) { + if (buf) { + if (n == size) { + memmove(buf, buf + 1, size - 1); + n--; + } + buf[n++] = c; + } + now = GetTimeInMillis(); + if ((INT32) (now - done) >= 0) + break; + } + return n; +} + +static int +MousePeekByte(Kbufio * b, int timeout) +{ + int c; + + c = MouseReadByte(b, timeout); + if (c != -1) + --b->used; + return c; +} +#endif /* NOTUSED */ + +static Bool +MouseWaitForWritable(int fd, int timeout) +{ + struct pollfd poll_fd; + int n; + + poll_fd.fd = fd; + poll_fd.events = POLLOUT; + n = xserver_poll(&poll_fd, 1, timeout); + if (n > 0) + return TRUE; + return FALSE; +} + +static Bool +MouseWriteByte(int fd, unsigned char c, int timeout) +{ + int ret; + +#ifdef DEBUG_BYTES + ErrorF("\tput %02x\n", c); +#endif + for (;;) { + ret = write(fd, &c, 1); + if (ret == 1) + return TRUE; + if (ret == 0) + return FALSE; + if (errno != EWOULDBLOCK) + return FALSE; + if (!MouseWaitForWritable(fd, timeout)) + return FALSE; + } +} + +static Bool +MouseWriteBytes(int fd, unsigned char *c, int n, int timeout) +{ + while (n--) + if (!MouseWriteByte(fd, *c++, timeout)) + return FALSE; + return TRUE; +} + +#define MAX_MOUSE 10 /* maximum length of mouse protocol */ +#define MAX_SKIP 16 /* number of error bytes before switching */ +#define MAX_VALID 4 /* number of valid packets before accepting */ + +typedef struct _kmouseProt { + const char *name; + Bool (*Complete) (KdPointerInfo * pi, unsigned char *ev, int ne); + int (*Valid) (KdPointerInfo * pi, unsigned char *ev, int ne); + Bool (*Parse) (KdPointerInfo * pi, unsigned char *ev, int ne); + Bool (*Init) (KdPointerInfo * pi); + unsigned char headerMask, headerValid; + unsigned char dataMask, dataValid; + Bool tty; + unsigned int c_iflag; + unsigned int c_oflag; + unsigned int c_lflag; + unsigned int c_cflag; + unsigned int speed; + unsigned char *init; + unsigned long state; +} KmouseProt; + +typedef enum _kmouseStage { + MouseBroken, MouseTesting, MouseWorking +} KmouseStage; + +typedef struct _kmouse { + Kbufio iob; + const KmouseProt *prot; + int i_prot; + KmouseStage stage; /* protocol verification stage */ + Bool tty; /* mouse device is a tty */ + int valid; /* sequential valid events */ + int tested; /* bytes scanned during Testing phase */ + int invalid; /* total invalid bytes for this protocol */ + unsigned long state; /* private per protocol, init to prot->state */ +} Kmouse; + +static int +mouseValid(KdPointerInfo * pi, unsigned char *ev, int ne) +{ + Kmouse *km = pi->driverPrivate; + const KmouseProt *prot = km->prot; + int i; + + for (i = 0; i < ne; i++) + if ((ev[i] & prot->headerMask) == prot->headerValid) + break; + if (i != 0) + return i; + for (i = 1; i < ne; i++) + if ((ev[i] & prot->dataMask) != prot->dataValid) + return -1; + return 0; +} + +static Bool +threeComplete(KdPointerInfo * pi, unsigned char *ev, int ne) +{ + return ne == 3; +} + +static Bool +fourComplete(KdPointerInfo * pi, unsigned char *ev, int ne) +{ + return ne == 4; +} + +static Bool +fiveComplete(KdPointerInfo * pi, unsigned char *ev, int ne) +{ + return ne == 5; +} + +static Bool +MouseReasonable(KdPointerInfo * pi, unsigned long flags, int dx, int dy) +{ + Kmouse *km = pi->driverPrivate; + + if (km->stage == MouseWorking) + return TRUE; + if (dx < -50 || dx > 50) { +#ifdef DEBUG + ErrorF("Large X %d\n", dx); +#endif + return FALSE; + } + if (dy < -50 || dy > 50) { +#ifdef DEBUG + ErrorF("Large Y %d\n", dy); +#endif + return FALSE; + } + return TRUE; +} + +/* + * Standard PS/2 mouse protocol + */ +static Bool +ps2Parse(KdPointerInfo * pi, unsigned char *ev, int ne) +{ + Kmouse *km = pi->driverPrivate; + int dx, dy, dz; + unsigned long flags; + unsigned long flagsrelease = 0; + + flags = KD_MOUSE_DELTA; + if (ev[0] & 4) + flags |= KD_BUTTON_2; + if (ev[0] & 2) + flags |= KD_BUTTON_3; + if (ev[0] & 1) + flags |= KD_BUTTON_1; + + if (ne > 3) { + dz = (int) (signed char) ev[3]; + if (dz < 0) { + flags |= KD_BUTTON_4; + flagsrelease = KD_BUTTON_4; + } + else if (dz > 0) { + flags |= KD_BUTTON_5; + flagsrelease = KD_BUTTON_5; + } + } + + dx = ev[1]; + if (ev[0] & 0x10) + dx -= 256; + dy = ev[2]; + if (ev[0] & 0x20) + dy -= 256; + dy = -dy; + if (!MouseReasonable(pi, flags, dx, dy)) + return FALSE; + if (km->stage == MouseWorking) { + KdEnqueuePointerEvent(pi, flags, dx, dy, 0); + if (flagsrelease) { + flags &= ~flagsrelease; + KdEnqueuePointerEvent(pi, flags, dx, dy, 0); + } + } + return TRUE; +} + +static Bool ps2Init(KdPointerInfo * pi); + +static const KmouseProt ps2Prot = { + "ps/2", + threeComplete, mouseValid, ps2Parse, ps2Init, + 0x08, 0x08, 0x00, 0x00, + FALSE +}; + +static const KmouseProt imps2Prot = { + "imps/2", + fourComplete, mouseValid, ps2Parse, ps2Init, + 0x08, 0x08, 0x00, 0x00, + FALSE +}; + +static const KmouseProt exps2Prot = { + "exps/2", + fourComplete, mouseValid, ps2Parse, ps2Init, + 0x08, 0x08, 0x00, 0x00, + FALSE +}; + +/* + * Once the mouse is known to speak ps/2 protocol, go and find out + * what advanced capabilities it has and turn them on + */ + +/* these extracted from FreeBSD 4.3 sys/dev/kbd/atkbdcreg.h */ + +/* aux device commands (sent to KBD_DATA_PORT) */ +#define PSMC_SET_SCALING11 0x00e6 +#define PSMC_SET_SCALING21 0x00e7 +#define PSMC_SET_RESOLUTION 0x00e8 +#define PSMC_SEND_DEV_STATUS 0x00e9 +#define PSMC_SET_STREAM_MODE 0x00ea +#define PSMC_SEND_DEV_DATA 0x00eb +#define PSMC_SET_REMOTE_MODE 0x00f0 +#define PSMC_SEND_DEV_ID 0x00f2 +#define PSMC_SET_SAMPLING_RATE 0x00f3 +#define PSMC_ENABLE_DEV 0x00f4 +#define PSMC_DISABLE_DEV 0x00f5 +#define PSMC_SET_DEFAULTS 0x00f6 +#define PSMC_RESET_DEV 0x00ff + +/* PSMC_SET_RESOLUTION argument */ +#define PSMD_RES_LOW 0 /* typically 25ppi */ +#define PSMD_RES_MEDIUM_LOW 1 /* typically 50ppi */ +#define PSMD_RES_MEDIUM_HIGH 2 /* typically 100ppi (default) */ +#define PSMD_RES_HIGH 3 /* typically 200ppi */ +#define PSMD_MAX_RESOLUTION PSMD_RES_HIGH + +/* PSMC_SET_SAMPLING_RATE */ +#define PSMD_MAX_RATE 255 /* FIXME: not sure if it's possible */ + +/* aux device ID */ +#define PSM_MOUSE_ID 0 +#define PSM_BALLPOINT_ID 2 +#define PSM_INTELLI_ID 3 +#define PSM_EXPLORER_ID 4 +#define PSM_4DMOUSE_ID 6 +#define PSM_4DPLUS_ID 8 + +static unsigned char ps2_init[] = { + PSMC_ENABLE_DEV +}; + +#define NINIT_PS2 1 + +static unsigned char wheel_3button_init[] = { + PSMC_SET_SAMPLING_RATE, 200, + PSMC_SET_SAMPLING_RATE, 100, + PSMC_SET_SAMPLING_RATE, 80, + PSMC_SEND_DEV_ID, +}; + +#define NINIT_IMPS2 4 + +static unsigned char wheel_5button_init[] = { + PSMC_SET_SAMPLING_RATE, 200, + PSMC_SET_SAMPLING_RATE, 100, + PSMC_SET_SAMPLING_RATE, 80, + PSMC_SET_SAMPLING_RATE, 200, + PSMC_SET_SAMPLING_RATE, 200, + PSMC_SET_SAMPLING_RATE, 80, + PSMC_SEND_DEV_ID, +}; + +#define NINIT_EXPS2 7 + +static unsigned char intelli_init[] = { + PSMC_SET_SAMPLING_RATE, 200, + PSMC_SET_SAMPLING_RATE, 100, + PSMC_SET_SAMPLING_RATE, 80, +}; + +#define NINIT_INTELLI 3 + +static int +ps2SkipInit(KdPointerInfo * pi, int ninit, Bool ret_next) +{ + Kmouse *km = pi->driverPrivate; + int c = -1; + Bool waiting; + + waiting = FALSE; + while (ninit || ret_next) { + c = MouseReadByte(&km->iob, 1); /* Minimum timeout like in xf86-input-mouse and tinyx */ + if (c == -1) + break; + /* look for ACK */ + if (c == 0xfa) { + ninit--; + if (ret_next) + waiting = TRUE; + } + /* look for packet start -- not the response */ + else if ((c & 0x08) == 0x08) + waiting = FALSE; + else if (waiting) + break; + } + return c; +} + +static Bool +ps2Init(KdPointerInfo * pi) +{ + Kmouse *km = pi->driverPrivate; + int id; + unsigned char *init; + int ninit; + int len; + + /* Send Intellimouse initialization sequence */ + MouseWriteBytes(km->iob.fd, intelli_init, sizeof(intelli_init), + 100); + /* + * Send ID command + */ + if (!MouseWriteByte(km->iob.fd, PSMC_SEND_DEV_ID, 100)) + return FALSE; + id = ps2SkipInit(pi, 0, TRUE); + switch (id) { + case 3: + init = wheel_3button_init; + ninit = NINIT_IMPS2; + km->prot = &imps2Prot; + len = sizeof(wheel_3button_init); + break; + case 4: + init = wheel_5button_init; + ninit = NINIT_EXPS2; + km->prot = &exps2Prot; + len = sizeof(wheel_5button_init); + break; + default: + init = ps2_init; + ninit = NINIT_PS2; + km->prot = &ps2Prot; + len = sizeof(ps2_init); + break; + } + if (init) + MouseWriteBytes(km->iob.fd, init, len, 100); + /* + * Flush out the available data to eliminate responses to the + * initialization string. Make sure any partial event is + * skipped + */ + (void) ps2SkipInit(pi, ninit, FALSE); + return TRUE; +} + +static Bool +busParse(KdPointerInfo * pi, unsigned char *ev, int ne) +{ + Kmouse *km = pi->driverPrivate; + int dx, dy; + unsigned long flags; + + flags = KD_MOUSE_DELTA; + dx = (signed char) ev[1]; + dy = -(signed char) ev[2]; + if ((ev[0] & 4) == 0) + flags |= KD_BUTTON_1; + if ((ev[0] & 2) == 0) + flags |= KD_BUTTON_2; + if ((ev[0] & 1) == 0) + flags |= KD_BUTTON_3; + if (!MouseReasonable(pi, flags, dx, dy)) + return FALSE; + if (km->stage == MouseWorking) + KdEnqueuePointerEvent(pi, flags, dx, dy, 0); + return TRUE; +} + +static const KmouseProt busProt = { + "bus", + threeComplete, mouseValid, busParse, 0, + 0xf8, 0x00, 0x00, 0x00, + FALSE +}; + +/* + * Standard MS serial protocol, three bytes + */ + +static Bool +msParse(KdPointerInfo * pi, unsigned char *ev, int ne) +{ + Kmouse *km = pi->driverPrivate; + int dx, dy; + unsigned long flags; + + flags = KD_MOUSE_DELTA; + + if (ev[0] & 0x20) + flags |= KD_BUTTON_1; + if (ev[0] & 0x10) + flags |= KD_BUTTON_3; + + dx = (signed char) (((ev[0] & 0x03) << 6) | (ev[1] & 0x3F)); + dy = (signed char) (((ev[0] & 0x0C) << 4) | (ev[2] & 0x3F)); + if (!MouseReasonable(pi, flags, dx, dy)) + return FALSE; + if (km->stage == MouseWorking) + KdEnqueuePointerEvent(pi, flags, dx, dy, 0); + return TRUE; +} + +static const KmouseProt msProt = { + "ms", + threeComplete, mouseValid, msParse, 0, + 0xc0, 0x40, 0xc0, 0x00, + TRUE, + IGNPAR, + 0, + 0, + CS7 | CSTOPB | CREAD | CLOCAL, + B1200, +}; + +/* + * Logitech mice send 3 or 4 bytes, the only way to tell is to look at the + * first byte of a synchronized protocol stream and see if it's got + * any bits turned on that can't occur in that fourth byte + */ +static Bool +logiComplete(KdPointerInfo * pi, unsigned char *ev, int ne) +{ + Kmouse *km = pi->driverPrivate; + + if ((ev[0] & 0x40) == 0x40) + return ne == 3; + if (km->stage != MouseBroken && (ev[0] & ~0x23) == 0) + return ne == 1; + return FALSE; +} + +static int +logiValid(KdPointerInfo * pi, unsigned char *ev, int ne) +{ + Kmouse *km = pi->driverPrivate; + const KmouseProt *prot = km->prot; + int i; + + for (i = 0; i < ne; i++) { + if ((ev[i] & 0x40) == 0x40) + break; + if (km->stage != MouseBroken && (ev[i] & ~0x23) == 0) + break; + } + if (i != 0) + return i; + for (i = 1; i < ne; i++) + if ((ev[i] & prot->dataMask) != prot->dataValid) + return -1; + return 0; +} + +static Bool +logiParse(KdPointerInfo * pi, unsigned char *ev, int ne) +{ + Kmouse *km = pi->driverPrivate; + int dx, dy; + unsigned long flags; + + flags = KD_MOUSE_DELTA; + + if (ne == 3) { + if (ev[0] & 0x20) + flags |= KD_BUTTON_1; + if (ev[0] & 0x10) + flags |= KD_BUTTON_3; + + dx = (signed char) (((ev[0] & 0x03) << 6) | (ev[1] & 0x3F)); + dy = (signed char) (((ev[0] & 0x0C) << 4) | (ev[2] & 0x3F)); + flags |= km->state & KD_BUTTON_2; + } + else { + if (ev[0] & 0x20) + flags |= KD_BUTTON_2; + dx = 0; + dy = 0; + flags |= km->state & (KD_BUTTON_1 | KD_BUTTON_3); + } + + if (!MouseReasonable(pi, flags, dx, dy)) + return FALSE; + if (km->stage == MouseWorking) + KdEnqueuePointerEvent(pi, flags, dx, dy, 0); + return TRUE; +} + +static const KmouseProt logiProt = { + "logitech", + logiComplete, logiValid, logiParse, 0, + 0xc0, 0x40, 0xc0, 0x00, + TRUE, + IGNPAR, + 0, + 0, + CS7 | CSTOPB | CREAD | CLOCAL, + B1200, +}; + +/* + * Mouse systems protocol, 5 bytes + */ +static Bool +mscParse(KdPointerInfo * pi, unsigned char *ev, int ne) +{ + Kmouse *km = pi->driverPrivate; + int dx, dy; + unsigned long flags; + + flags = KD_MOUSE_DELTA; + + if (!(ev[0] & 0x4)) + flags |= KD_BUTTON_1; + if (!(ev[0] & 0x2)) + flags |= KD_BUTTON_2; + if (!(ev[0] & 0x1)) + flags |= KD_BUTTON_3; + dx = (signed char) (ev[1]) + (signed char) (ev[3]); + dy = -((signed char) (ev[2]) + (signed char) (ev[4])); + + if (!MouseReasonable(pi, flags, dx, dy)) + return FALSE; + if (km->stage == MouseWorking) + KdEnqueuePointerEvent(pi, flags, dx, dy, 0); + return TRUE; +} + +static const KmouseProt mscProt = { + "msc", + fiveComplete, mouseValid, mscParse, 0, + 0xf8, 0x80, 0x00, 0x00, + TRUE, + IGNPAR, + 0, + 0, + CS8 | CSTOPB | CREAD | CLOCAL, + B1200, +}; + +/* + * Use logitech before ms -- they're the same except that + * logitech sometimes has a fourth byte + */ +static const KmouseProt *kmouseProts[] = { + &ps2Prot, &imps2Prot, &exps2Prot, &busProt, &logiProt, &msProt, &mscProt, +}; + +#define NUM_PROT (sizeof (kmouseProts) / sizeof (kmouseProts[0])) + +static void +MouseInitProtocol(Kmouse * km) +{ + int ret; + struct termios t; + + if (km->prot->tty) { + ret = tcgetattr(km->iob.fd, &t); + + if (ret >= 0) { + t.c_iflag = km->prot->c_iflag; + t.c_oflag = km->prot->c_oflag; + t.c_lflag = km->prot->c_lflag; + t.c_cflag = km->prot->c_cflag; + cfsetispeed(&t, km->prot->speed); + cfsetospeed(&t, km->prot->speed); + ret = tcsetattr(km->iob.fd, TCSANOW, &t); + } + } + km->stage = MouseBroken; + km->valid = 0; + km->tested = 0; + km->invalid = 0; + km->state = km->prot->state; +} + +static void +MouseFirstProtocol(Kmouse * km, const char *prot) +{ + if (prot) { + for (km->i_prot = 0; km->i_prot < NUM_PROT; km->i_prot++) + if (!strcmp(prot, kmouseProts[km->i_prot]->name)) + break; + if (km->i_prot == NUM_PROT) { + int i; + + ErrorF("Unknown mouse protocol \"%s\". Pick one of:", prot); + for (i = 0; i < NUM_PROT; i++) + ErrorF(" %s", kmouseProts[i]->name); + ErrorF("\n"); + km->i_prot = 0; + km->prot = kmouseProts[km->i_prot]; + ErrorF("Falling back to %s\n", km->prot->name); + } + else { + km->prot = kmouseProts[km->i_prot]; + if (km->tty && !km->prot->tty) + ErrorF + ("Mouse device is serial port, protocol %s is not serial protocol\n", + prot); + else if (!km->tty && km->prot->tty) + ErrorF + ("Mouse device is not serial port, protocol %s is serial protocol\n", + prot); + } + } + if (!km->prot) { + for (km->i_prot = 0; kmouseProts[km->i_prot]->tty != km->tty; + km->i_prot++); + km->prot = kmouseProts[km->i_prot]; + } + MouseInitProtocol(km); +} + +static void +MouseNextProtocol(Kmouse * km) +{ + do { + if (!km->prot) + km->i_prot = 0; + else if (++km->i_prot >= NUM_PROT) + km->i_prot = 0; + km->prot = kmouseProts[km->i_prot]; + } while (km->prot->tty != km->tty); + MouseInitProtocol(km); + ErrorF("Switching to mouse protocol \"%s\"\n", km->prot->name); +} + +static void +MouseRead(int mousePort, void *closure) +{ + KdPointerInfo *pi = closure; + Kmouse *km = pi->driverPrivate; + unsigned char event[MAX_MOUSE]; + int ne; + int c; + int i; + int timeout; + + timeout = 0; + ne = 0; + for (;;) { + c = MouseReadByte(&km->iob, timeout); + if (c == -1) { + if (ne) { + km->invalid += ne + km->tested; + km->valid = 0; + km->tested = 0; + km->stage = MouseBroken; + } + break; + } + event[ne++] = c; + i = (*km->prot->Valid) (pi, event, ne); + if (i != 0) { +#ifdef DEBUG + ErrorF("Mouse protocol %s broken %d of %d bytes bad\n", + km->prot->name, i > 0 ? i : ne, ne); +#endif + if (i > 0 && i < ne) { + ne -= i; + memmove(event, event + i, ne); + } + else { + i = ne; + ne = 0; + } + km->invalid += i + km->tested; + km->valid = 0; + km->tested = 0; + if (km->stage == MouseWorking) + km->i_prot--; + km->stage = MouseBroken; + if (km->invalid > MAX_SKIP) { + MouseNextProtocol(km); + ne = 0; + } + timeout = 0; + } + else { + if ((*km->prot->Complete) (pi, event, ne)) { + if ((*km->prot->Parse) (pi, event, ne)) { + switch (km->stage) { + case MouseBroken: +#ifdef DEBUG + ErrorF("Mouse protocol %s seems OK\n", km->prot->name); +#endif + /* do not zero invalid to accumulate invalid bytes */ + km->valid = 0; + km->tested = 0; + km->stage = MouseTesting; + /* fall through ... */ + case MouseTesting: + km->valid++; + km->tested += ne; + if (km->valid > MAX_VALID) { +#ifdef DEBUG + ErrorF("Mouse protocol %s working\n", + km->prot->name); +#endif + km->stage = MouseWorking; + km->invalid = 0; + km->tested = 0; + km->valid = 0; + if (km->prot->Init && !(*km->prot->Init) (pi)) + km->stage = MouseBroken; + } + break; + case MouseWorking: + break; + } + } + else { + km->invalid += ne + km->tested; + km->valid = 0; + km->tested = 0; + km->stage = MouseBroken; + } + ne = 0; + timeout = 0; + } + else + timeout = MOUSE_TIMEOUT; + } + } +} + +const char *kdefaultMouse[] = { + "/dev/input/mice", + "/dev/mouse", + "/dev/psaux", + "/dev/adbmouse", + "/dev/ttyS0", + "/dev/ttyS1", +}; + +#define NUM_DEFAULT_MOUSE (sizeof (kdefaultMouse) / sizeof (kdefaultMouse[0])) + +static Status +MouseInit(KdPointerInfo * pi) +{ + int i; + int fd; + Kmouse *km; + + if (!pi) + return BadImplementation; + + if (!pi->path || strcmp(pi->path, "auto") == 0) { + for (i = 0; i < NUM_DEFAULT_MOUSE; i++) { + fd = open(kdefaultMouse[i], 2); + if (fd >= 0) { + pi->path = strdup(kdefaultMouse[i]); + break; + } + } + } + else { + fd = open(pi->path, 2); + } + + if (fd < 0) + return BadMatch; + + km = (Kmouse *) malloc(sizeof(Kmouse)); + if (km) { + km->iob.avail = km->iob.used = 0; + MouseFirstProtocol(km, pi->protocol ? pi->protocol : "ps/2"); + /* MouseFirstProtocol sets state to MouseBroken for later protocol + * checks. Skip these checks if a protocol was supplied */ + if (pi->protocol) + km->state = MouseWorking; + km->i_prot = 0; + km->tty = isatty(fd); + km->iob.fd = fd; + pi->driverPrivate = km; + } + else { + close(fd); + return BadAlloc; + } + + return Success; +} + +static Status +MouseEnable(KdPointerInfo * pi) +{ + Kmouse *km; + + if (!pi || !pi->driverPrivate || !pi->path) + return BadImplementation; + + km = pi->driverPrivate; + + km->iob.fd = open(pi->path, 2); + if (km->iob.fd < 0) + return BadMatch; + + if (!KdRegisterFd(km->iob.fd, MouseRead, pi)) { + close(km->iob.fd); + return BadAlloc; + } + + return Success; +} + +static void +MouseDisable(KdPointerInfo * pi) +{ + Kmouse *km; + + if (!pi || !pi->driverPrivate) + return; + + km = pi->driverPrivate; + KdUnregisterFd(pi, km->iob.fd, TRUE); +} + +static void +MouseFini(KdPointerInfo * pi) +{ + free(pi->driverPrivate); + pi->driverPrivate = NULL; +} + +KdPointerDriver LinuxMouseDriver = { + .name = "mouse", + .Init = MouseInit, + .Enable = MouseEnable, + .Disable = MouseDisable, + .Fini = MouseFini, +}; diff --git a/hw/kdrive/linux/ms.c b/hw/kdrive/linux/ms.c new file mode 100644 index 0000000000..6085f4e40b --- /dev/null +++ b/hw/kdrive/linux/ms.c @@ -0,0 +1,172 @@ +/* +Copyright (c) 2001 by Juliusz Chroboczek +Copyright (c) 1999 by Keith Packard + +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 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. +*/ + +#include +#include +#include +#include "os/xserver_poll.h" +#include +#include +#include "inputstr.h" +#include "scrnintstr.h" +#include "kdrive.h" + +static int +MsReadBytes(int fd, char *buf, int len, int min) +{ + int n, tot; + struct pollfd poll_fd; + + poll_fd.fd = fd; + poll_fd.events = POLLIN; + tot = 0; + while (len) { + n = read(fd, buf, len); + if (n > 0) { + tot += n; + buf += n; + len -= n; + } + if (tot % min == 0) + break; + n = xserver_poll(&poll_fd, 1, 100); + if (n <= 0) + break; + } + return tot; +} + +static void +MsRead(int port, void *closure) +{ + unsigned char buf[3 * 200]; + unsigned char *b; + int n; + int dx, dy; + unsigned long flags; + + while ((n = MsReadBytes(port, (char *) buf, sizeof(buf), 3)) > 0) { + b = buf; + while (n >= 3) { + flags = KD_MOUSE_DELTA; + + if (b[0] & 0x20) + flags |= KD_BUTTON_1; + if (b[0] & 0x10) + flags |= KD_BUTTON_3; + + dx = (char) (((b[0] & 0x03) << 6) | (b[1] & 0x3F)); + dy = (char) (((b[0] & 0x0C) << 4) | (b[2] & 0x3F)); + n -= 3; + b += 3; + KdEnqueuePointerEvent(closure, flags, dx, dy, 0); + } + } +} + +static Status +MsInit(KdPointerInfo * pi) +{ + if (!pi) + return BadImplementation; + + if (!pi->path || strcmp(pi->path, "auto")) + pi->path = strdup("/dev/mouse"); + if (!pi->name) + pi->name = strdup("Microsoft protocol mouse"); + + return Success; +} + +static Status +MsEnable(KdPointerInfo * pi) +{ + int port; + struct termios t; + int ret; + + port = open(pi->path, O_RDWR | O_NONBLOCK); + if (port < 0) { + ErrorF("Couldn't open %s (%d)\n", pi->path, (int) errno); + return 0; + } + else if (port == 0) { + ErrorF("Opening %s returned 0! Please complain to Keith.\n", pi->path); + goto bail; + } + + if (!isatty(port)) { + ErrorF("%s is not a tty\n", pi->path); + goto bail; + } + + ret = tcgetattr(port, &t); + if (ret < 0) { + ErrorF("Couldn't tcgetattr(%s): %d\n", pi->path, errno); + goto bail; + } + t.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | + IGNCR | ICRNL | IXON | IXOFF); + t.c_oflag &= ~OPOST; + t.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); + t.c_cflag &= ~(CSIZE | PARENB); + t.c_cflag |= CS8 | CLOCAL | CSTOPB; + + cfsetispeed(&t, B1200); + cfsetospeed(&t, B1200); + t.c_cc[VMIN] = 1; + t.c_cc[VTIME] = 0; + ret = tcsetattr(port, TCSANOW, &t); + if (ret < 0) { + ErrorF("Couldn't tcsetattr(%s): %d\n", pi->path, errno); + goto bail; + } + if (KdRegisterFd(port, MsRead, pi)) + return TRUE; + pi->driverPrivate = (void *) (intptr_t) port; + + return Success; + + bail: + close(port); + return BadMatch; +} + +static void +MsDisable(KdPointerInfo * pi) +{ + KdUnregisterFd(pi, (int) (intptr_t) pi->driverPrivate, TRUE); +} + +static void +MsFini(KdPointerInfo * pi) +{ +} + +KdPointerDriver MsMouseDriver = { + .name = "ms", + .Init = MsInit, + .Enable = MsEnable, + .Disable = MsDisable, + .Fini = MsFini, +}; diff --git a/hw/kdrive/linux/ps2.c b/hw/kdrive/linux/ps2.c new file mode 100644 index 0000000000..94def4c656 --- /dev/null +++ b/hw/kdrive/linux/ps2.c @@ -0,0 +1,174 @@ +/* + * Copyright © 1999 Keith Packard + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of Keith Packard not be used in + * advertising or publicity pertaining to distribution of the software without + * specific, written prior permission. Keith Packard makes no + * representations about the suitability of this software for any purpose. It + * is provided "as is" without express or implied warranty. + * + * KEITH PACKARD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL KEITH PACKARD BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include "os/xserver_poll.h" +#include "inputstr.h" +#include "scrnintstr.h" +#include "kdrive.h" + +static int +Ps2ReadBytes(int fd, char *buf, int len, int min) +{ + int n, tot; + struct pollfd poll_fd; + + tot = 0; + poll_fd.fd = fd; + poll_fd.events = POLLIN; + while (len) { + n = read(fd, buf, len); + if (n > 0) { + tot += n; + buf += n; + len -= n; + } + if (tot % min == 0) + break; + n = xserver_poll(&poll_fd, 1, 100); + if (n <= 0) + break; + } + return tot; +} + +const char *Ps2Names[] = { + "/dev/psaux", +/* "/dev/mouse", */ + "/dev/input/mice", +}; + +#define NUM_PS2_NAMES (sizeof (Ps2Names) / sizeof (Ps2Names[0])) + +static void +Ps2Read(int ps2Port, void *closure) +{ + unsigned char buf[3 * 200]; + unsigned char *b; + int n; + int dx, dy; + unsigned long flags; + unsigned long left_button = KD_BUTTON_1; + unsigned long right_button = KD_BUTTON_3; + +#undef SWAP_USB +#ifdef SWAP_USB + if (id == 2) { + left_button = KD_BUTTON_3; + right_button = KD_BUTTON_1; + } +#endif + while ((n = Ps2ReadBytes(ps2Port, (char *) buf, sizeof(buf), 3)) > 0) { + b = buf; + while (n >= 3) { + flags = KD_MOUSE_DELTA; + if (b[0] & 4) + flags |= KD_BUTTON_2; + if (b[0] & 2) + flags |= right_button; + if (b[0] & 1) + flags |= left_button; + + dx = b[1]; + if (b[0] & 0x10) + dx -= 256; + dy = b[2]; + if (b[0] & 0x20) + dy -= 256; + dy = -dy; + n -= 3; + b += 3; + KdEnqueuePointerEvent(closure, flags, dx, dy, 0); + } + } +} + +static Status +Ps2Init(KdPointerInfo * pi) +{ + int ps2Port, i; + + if (!pi->path) { + for (i = 0; i < NUM_PS2_NAMES; i++) { + ps2Port = open(Ps2Names[i], 0); + if (ps2Port >= 0) { + pi->path = strdup(Ps2Names[i]); + break; + } + } + } + else { + ps2Port = open(pi->path, 0); + } + + if (ps2Port < 0) + return BadMatch; + + close(ps2Port); + if (!pi->name) + pi->name = strdup("PS/2 Mouse"); + + return Success; +} + +static Status +Ps2Enable(KdPointerInfo * pi) +{ + int fd; + + if (!pi) + return BadImplementation; + + fd = open(pi->path, 0); + if (fd < 0) + return BadMatch; + + if (!KdRegisterFd(fd, Ps2Read, pi)) { + close(fd); + return BadAlloc; + } + + pi->driverPrivate = (void *) (intptr_t) fd; + + return Success; +} + +static void +Ps2Disable(KdPointerInfo * pi) +{ + KdUnregisterFd(pi, (int) (intptr_t) pi->driverPrivate, TRUE); +} + +static void +Ps2Fini(KdPointerInfo * pi) +{ +} + +KdPointerDriver Ps2MouseDriver = { + .name = "ps2", + .Init = Ps2Init, + .Enable = Ps2Enable, + .Disable = Ps2Disable, + .Fini = Ps2Fini, +}; diff --git a/hw/kdrive/linux/tslib.c b/hw/kdrive/linux/tslib.c new file mode 100644 index 0000000000..18108f9266 --- /dev/null +++ b/hw/kdrive/linux/tslib.c @@ -0,0 +1,191 @@ +/* + * TSLIB based touchscreen driver for KDrive + * Porting to new input API and event queueing by Daniel Stone. + * Derived from ts.c by Keith Packard + * Derived from ps2.c by Jim Gettys + * + * Copyright © 1999 Keith Packard + * Copyright © 2000 Compaq Computer Corporation + * Copyright © 2002 MontaVista Software Inc. + * Copyright © 2005 OpenedHand Ltd. + * Copyright © 2006 Nokia Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the authors and/or copyright holders + * not be used in advertising or publicity pertaining to distribution of the + * software without specific, written prior permission. The authors and/or + * copyright holders make no representations about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + * + * THE AUTHORS AND/OR COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS, IN NO EVENT SHALL THE AUTHORS AND/OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include +#include +#include "inputstr.h" +#include "scrnintstr.h" +#include "kdrive.h" +#include +#include +#include +#include + +struct TslibPrivate { + int fd; + int lastx, lasty; + struct tsdev *tsDev; + void (*raw_event_hook) (int x, int y, int pressure, void *closure); + void *raw_event_closure; + int phys_screen; +}; + +static void +TsRead(int fd, void *closure) +{ + KdPointerInfo *pi = closure; + struct TslibPrivate *private = pi->driverPrivate; + struct ts_sample event; + long x = 0, y = 0; + unsigned long flags; + + if (private->raw_event_hook) { + while (ts_read_raw(private->tsDev, &event, 1) == 1) + private->raw_event_hook(event.x, event.y, event.pressure, + private->raw_event_closure); + return; + } + + while (ts_read(private->tsDev, &event, 1) == 1) { + if (event.pressure) { + flags = KD_BUTTON_1; + + /* + * Here we test for the touch screen driver actually being on the + * touch screen, if it is we send absolute coordinates. If not, + * then we send delta's so that we can track the entire vga screen. + */ + if (KdCurScreen == private->phys_screen) { + x = event.x; + y = event.y; + } + else { + flags |= KD_MOUSE_DELTA; + if ((private->lastx == 0) || (private->lasty == 0)) { + x = event.x; + y = event.y; + } + else { + x = event.x - private->lastx; + y = event.y - private->lasty; + } + } + private->lastx = event.x; + private->lasty = event.y; + } + else { + flags = 0; + x = private->lastx; + y = private->lasty; + } + + KdEnqueuePointerEvent(pi, flags, x, y, event.pressure); + } +} + +static Status +TslibEnable(KdPointerInfo * pi) +{ + struct TslibPrivate *private = pi->driverPrivate; + + private->raw_event_hook = NULL; + private->raw_event_closure = NULL; + if (!pi->path) { + pi->path = strdup("/dev/input/touchscreen0"); + ErrorF("[tslib/TslibEnable] no device path given, trying %s\n", + pi->path); + } + + private->tsDev = ts_open(pi->path, 0); + if (!private->tsDev) { + ErrorF("[tslib/TslibEnable] failed to open %s\n", pi->path); + return BadAlloc; + } + + if (ts_config(private->tsDev)) { + ErrorF("[tslib/TslibEnable] failed to load configuration\n"); + ts_close(private->tsDev); + private->tsDev = NULL; + return BadValue; + } + + private->fd = ts_fd(private->tsDev); + + KdRegisterFd(private->fd, TsRead, pi); + + return Success; +} + +static void +TslibDisable(KdPointerInfo * pi) +{ + struct TslibPrivate *private = pi->driverPrivate; + + if (private->fd) + KdUnregisterFd(pi, private->fd, TRUE); + + if (private->tsDev) + ts_close(private->tsDev); + + private->fd = 0; + private->tsDev = NULL; +} + +static Status +TslibInit(KdPointerInfo * pi) +{ + struct TslibPrivate *private = NULL; + + if (!pi || !pi->dixdev) + return !Success; + + pi->driverPrivate = (struct TslibPrivate *) + calloc(sizeof(struct TslibPrivate), 1); + if (!pi->driverPrivate) + return !Success; + + private = pi->driverPrivate; + /* hacktastic */ + private->phys_screen = 0; + pi->nAxes = 3; + pi->name = strdup("Touchscreen"); + pi->inputClass = KD_TOUCHSCREEN; + + return Success; +} + +static void +TslibFini(KdPointerInfo * pi) +{ + free(pi->driverPrivate); + pi->driverPrivate = NULL; +} + +KdPointerDriver TsDriver = { + .name = "tslib", + .Init = TslibInit, + .Enable = TslibEnable, + .Disable = TslibDisable, + .Fini = TslibFini, +}; diff --git a/hw/kdrive/meson.build b/hw/kdrive/meson.build index 0f4a43cc31..83195a76de 100644 --- a/hw/kdrive/meson.build +++ b/hw/kdrive/meson.build @@ -1,11 +1,20 @@ +build_xfbdev = get_option('xfbdev') build_xephyr = get_option('xephyr') -build_kdrive = build_xephyr +build_kdrive = build_xephyr or build_xfbdev build_kdrive_kbd = false build_kdrive_mouse = false build_kdrive_evdev = false build_kdrive_tslib = false +if build_xfbdev + build_kdrive_kbd = get_option('kdrive_kbd') + build_kdrive_mouse = get_option('kdrive_mouse') + build_kdrive_evdev = get_option('kdrive_evdev') + build_kdrive_tslib = get_option('kdrive_tslib') + tslib_dep = dependency('tslib', required : build_kdrive_tslib) +endif + if build_kdrive subdir('src') endif @@ -13,3 +22,8 @@ endif if build_xephyr subdir('ephyr') endif + +if build_xfbdev + subdir('linux') + subdir('fbdev') +endif diff --git a/meson.build b/meson.build index 6152a3d61a..279dba255e 100644 --- a/meson.build +++ b/meson.build @@ -249,6 +249,7 @@ summary({ 'Xwin': build_xwin, 'Xquartz': build_xquartz, 'Xephyr': get_option('xephyr'), + 'Xfbdev': get_option('xfbdev'), }, section: 'DDX', bool_yn: true, diff --git a/meson_options.txt b/meson_options.txt index 87c67ec005..343ef3929b 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -2,6 +2,8 @@ option('xorg', type: 'combo', choices: ['true', 'false', 'auto'], value: 'auto', description: 'Enable Xorg X Server') option('xephyr', type: 'boolean', value: false, description: 'Enable Xephyr nested X server') +option('xfbdev', type: 'boolean', value: false, + description: 'Enable the kdrive framebuffer device server') option('glamor', type: 'combo', choices: ['true', 'false', 'auto'], value: 'auto', description: 'Enable glamor (default yes for Xorg builds)') option('xnest', type: 'combo', choices: ['true', 'false', 'auto'], value: 'auto', @@ -13,6 +15,15 @@ option('xwin', type: 'combo', choices: ['true', 'false', 'auto'], value: 'auto', option('xquartz', type: 'combo', choices: ['true', 'false', 'auto'], value: 'auto', description: 'Enable Xquartz X server') +option('kdrive_kbd', type: 'boolean', value: true, + description: 'Build kbd driver for kdrive') +option('kdrive_mouse', type: 'boolean', value: true, + description: 'Build mouse driver for kdrive') +option('kdrive_evdev', type: 'boolean', value: true, + description: 'Build evdev driver for kdrive') +option('kdrive_tslib', type: 'boolean', value: false, + description: 'Build kdrive tslib touchscreen support') + option('builder_addr', type: 'string', description: 'Builder address', value: 'xlibre@freelists.org') option('builder_string', type: 'string', description: 'Additional builder string')