/*
 * kdmapop.c - export getscrnmap(), loadscrnmap(),
 *                    loaduniscrnmap(), loadunimap()
 *
 * Hide the ioctl use in this file.
 */
#include "config.h"

#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <linux/kd.h>
#include "kdmapop.h"
#include "nls.h"
#include "version.h"

/*
 * Linux pre-0.96 defined GIO_SCRNMAP, PIO_SCRNMAP:
	typedef char scrnmap_t;
	#define E_TABSZ         256
	#define GIO_SCRNMAP     0x4B40
	#define PIO_SCRNMAP     0x4B41
 * and Linux 0.99.14y first implemented them.
 * Usage:
	scrnmap_t map[E_TABSZ];
	ioctl(fd,GIO_SCRNMAP,map);
	ioctl(fd,PIO_SCRNMAP,map);
 * to read or write the kernel translation table that is
 * applied to user application output before displaying.
 *
 * Before 1.3.1, the character set was completely undetermined,
 * and if the font was in an order different from the character
 * set in use, the user screen map was used to map application
 * codes to font indices. (To be more precise: there were four
 * translation tables, and this ioctl would get/set the fourth
 * table, while the other three tables are built-in and constant.)
 */
int getscrnmap(int fd, char *map)
{
	if (ioctl(fd, GIO_SCRNMAP, map)) {
		perror("GIO_SCRNMAP");
		return -1;
	}
	return 0;
}

int loadscrnmap(int fd, char *map)
{
	if (ioctl(fd, PIO_SCRNMAP, map)) {
		perror("PIO_SCRNMAP");
		return -1;
	}
	return 0;
}

/*
 * Linux 1.3.1 introduces GIO_UNISCRNMAP, PIO_UNISCRNMAP:
	#define GIO_UNISCRNMAP  0x4B69
	#define PIO_UNISCRNMAP  0x4B6A
 * Usage:
	unsigned short umap[E_TABSZ];
	ioctl(fd,GIO_UNISCRNMAP,umap);
	ioctl(fd,PIO_UNISCRNMAP,umap);
 * to read or write the kernel translation table that is
 * applied to user application output before displaying.
 * (When the console is not in utf8 mode.)
 *
 * The idea is that the umap values are 16-bit unicode (ucs2)
 * values, and that the fonts will have an index giving the
 * unicode value for each glyph, so that the kernel can match up
 * application codes to font positions.
	#define UNI_DIRECT_BASE 0xF000
	#define UNI_DIRECT_MASK 0x01FF
 * For compatibility, and for fonts without table, the unicode
 * values 0xF000+n, 0 <= n <= 0x01FF, acts as direct font indices.
 * In the new scheme, the old PIO_SCRNMAP fills the kernel umap
 * table with such direct-to-font values.
 */

int getuniscrnmap(int fd, unsigned short *map)
{
	if (ioctl(fd, GIO_UNISCRNMAP, map)) {
		perror("GIO_UNISCRNMAP");
		return -1;
	}
	return 0;
}

int loaduniscrnmap(int fd, unsigned short *map)
{
	if (ioctl(fd, PIO_UNISCRNMAP, map)) {
		perror("PIO_UNISCRNMAP");
		return -1;
	}
	return 0;
}

/*
 * Linux 1.1.63 introduces GIO_UNIMAP, PIO_UNIMAP, PIO_UNIMAPCLR:
	#define GIO_UNIMAP     0x4B66
	#define PIO_UNIMAP     0x4B67
	#define PIO_UNIMAPCLR  0x4B68
 * And Linux 1.1.92 implements them.
 * Usage:
	struct unimapinit {
        	unsigned short advised_hashsize;
		unsigned short advised_hashstep;
		unsigned short advised_hashlevel;
	} ui;
	ioctl(fd, PIO_UNIMAPCLR, &ui);
 * to clear the unimap table and advise about the kind of
 * hash setup appropriate to what one is going to load
 * (with 0 for "don't care").
	struct unipair {
	        unsigned short unicode;
		unsigned short fontpos;
	};
	struct unimapdesc {
        	unsigned short entry_ct;
		struct unipair *entries;
	} ud;
	ioctl(fd, PIO_UNIMAP, &ud);
	ioctl(fd, GIO_UNIMAP, &ud);
 * to add the indicated pairs to the kernel unimap table
 * or to read the kernel unimap table, where in the latter case
 * ud.entry_ct indicated the room available.
 *
 * In Linux 1.3.28 the hash table was replaced by a 3-level
 * paged table, so the contents of a struct unimapinit are
 * no longer meaningful.
 *
 * Linux 2.6.1 makes GIO_UNIMAP, PIO_UNIMAP, PIO_UNIMAPCLR per-vt
 * so that fd no longer is random.
 */
int getunimap(int fd, struct unimapdesc *ud0)
{
	struct unimapdesc ud;
	int ct;

	ud.entry_ct = 0;
	ud.entries  = 0;
	if (ioctl(fd, GIO_UNIMAP, &ud)) {
		if (errno != ENOMEM || ud.entry_ct == 0) {
			perror("GIO_UNIMAP(0)");
			return -1;
		}
		ct         = ud.entry_ct;
		ud.entries = (struct unipair *)
		    malloc(ct * sizeof(struct unipair));
		if (ud.entries == NULL) {
			fprintf(stderr, _("%s: out of memory\n"), progname);
			return -1;
		}
		if (ioctl(fd, GIO_UNIMAP, &ud)) {
			perror("GIO_UNIMAP");
			free(ud.entries);
			return -1;
		}
		if (ct != ud.entry_ct)
			fprintf(stderr,
			        _("strange... ct changed from %d to %d\n"),
			        ct, ud.entry_ct);
		/* someone could change the unimap between our
		   first and second ioctl, so the above errors
		   are not impossible */
	}
	*ud0 = ud;
	return 0;
}

int loadunimap(int fd, struct unimapinit *ui, struct unimapdesc *ud)
{
	struct unimapinit advice;

	if (ui)
		advice = *ui;
	else {
		advice.advised_hashsize  = 0;
		advice.advised_hashstep  = 0;
		advice.advised_hashlevel = 0;
	}
again:
	if (ioctl(fd, PIO_UNIMAPCLR, &advice)) {
#ifdef ENOIOCTLCMD
		if (errno == ENOIOCTLCMD) {
			fprintf(stderr,
			        _("It seems this kernel is older than 1.1.92\n"
			          "No Unicode mapping table loaded.\n"));
		} else
#endif
			perror("PIO_UNIMAPCLR");
		return -1;
	}
	if (ud == NULL)
		return 0;

	if (ioctl(fd, PIO_UNIMAP, ud)) {
		if (errno == ENOMEM && advice.advised_hashlevel < 100) {
			advice.advised_hashlevel++;
			goto again;
		}
		perror("PIO_UNIMAP");
		return -1;
	}

	return 0;
}
