aboutsummaryrefslogtreecommitdiffstats
path: root/kernel/audit.h
Commit message (Collapse)AuthorAge
* Fix rule eviction order for AUDIT_DIRAl Viro2009-06-24
| | | | | | | | | If syscall removes the root of subtree being watched, we definitely do not want the rules refering that subtree to be destroyed without the syscall in question having a chance to match them. Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
* Audit: move audit_get_nd completely into audit_watchEric Paris2009-06-23
| | | | | | | | audit_get_nd() is only used by audit_watch and could be more cleanly implemented by having the audit watch functions call it when needed rather than making the generic audit rule parsing code deal with those objects. Signed-off-by: Eric Paris <eparis@redhat.com>
* audit: seperate audit inode watches into a subfileEric Paris2009-06-23
| | | | | | | | In preparation for converting audit to use fsnotify instead of inotify we seperate the inode watching code into it's own file. This is similar to how the audit tree watching code is already seperated into audit_tree.c Signed-off-by: Eric Paris <eparis@redhat.com>
* fixing audit rule ordering mess, part 1Al Viro2009-01-04
| | | | | | | | | | | | Problem: ordering between the rules on exit chain is currently lost; all watch and inode rules are listed after everything else _and_ exit,never on one kind doesn't stop exit,always on another from being matched. Solution: assign priorities to rules, keep track of the current highest-priority matching rule and its result (always/never). Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
* [PATCH 1/2] audit: move extern declarations to audit.hHarvey Harrison2008-04-28
| | | | | | | | | | | | | | | Leave audit_sig_{uid|pid|sid} protected by #ifdef CONFIG_AUDITSYSCALL. Noticed by sparse: kernel/audit.c:73:6: warning: symbol 'audit_ever_enabled' was not declared. Should it be static? kernel/audit.c:100:8: warning: symbol 'audit_sig_uid' was not declared. Should it be static? kernel/audit.c:101:8: warning: symbol 'audit_sig_pid' was not declared. Should it be static? kernel/audit.c:102:6: warning: symbol 'audit_sig_sid' was not declared. Should it be static? kernel/audit.c:117:23: warning: symbol 'audit_ih' was not declared. Should it be static? kernel/auditfilter.c:78:18: warning: symbol 'audit_filter_list' was not declared. Should it be static? Signed-off-by: Harvey Harrison <harvey.harrison@gmail.com> Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
* SELinux: use new audit hooks, remove redundant exportsAhmed S. Darwish2008-04-18
| | | | | | | | | | | | | Setup the new Audit LSM hooks for SELinux. Remove the now redundant exported SELinux Audit interface. Audit: Export 'audit_krule' and 'audit_field' to the public since their internals are needed by the implementation of the new LSM hook 'audit_rule_known'. Signed-off-by: Casey Schaufler <casey@schaufler-ca.com> Signed-off-by: Ahmed S. Darwish <darwish.07@gmail.com> Acked-by: James Morris <jmorris@namei.org>
* [PATCH] audit: watching subtreesAl Viro2007-10-21
| | | | | | | | | | | | | | | | | | | New kind of audit rule predicates: "object is visible in given subtree". The part that can be sanely implemented, that is. Limitations: * if you have hardlink from outside of tree, you'd better watch it too (or just watch the object itself, obviously) * if you mount something under a watched tree, tell audit that new chunk should be added to watched subtrees * if you umount something in a watched tree and it's still mounted elsewhere, you will get matches on events happening there. New command tells audit to recalculate the trees, trimming such sources of false positives. Note that it's _not_ about path - if something mounted in several places (multiple mount, bindings, different namespaces, etc.), the match does _not_ depend on which one we are using for access. Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
* Audit: add TTY input auditingMiloslav Trmac2007-07-16
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Add TTY input auditing, used to audit system administrator's actions. This is required by various security standards such as DCID 6/3 and PCI to provide non-repudiation of administrator's actions and to allow a review of past actions if the administrator seems to overstep their duties or if the system becomes misconfigured for unknown reasons. These requirements do not make it necessary to audit TTY output as well. Compared to an user-space keylogger, this approach records TTY input using the audit subsystem, correlated with other audit events, and it is completely transparent to the user-space application (e.g. the console ioctls still work). TTY input auditing works on a higher level than auditing all system calls within the session, which would produce an overwhelming amount of mostly useless audit events. Add an "audit_tty" attribute, inherited across fork (). Data read from TTYs by process with the attribute is sent to the audit subsystem by the kernel. The audit netlink interface is extended to allow modifying the audit_tty attribute, and to allow sending explanatory audit events from user-space (for example, a shell might send an event containing the final command, after the interactive command-line editing and history expansion is performed, which might be difficult to decipher from the TTY input alone). Because the "audit_tty" attribute is inherited across fork (), it would be set e.g. for sshd restarted within an audited session. To prevent this, the audit_tty attribute is cleared when a process with no open TTY file descriptors (e.g. after daemon startup) opens a TTY. See https://www.redhat.com/archives/linux-audit/2007-June/msg00000.html for a more detailed rationale document for an older version of this patch. [akpm@linux-foundation.org: build fix] Signed-off-by: Miloslav Trmac <mitr@redhat.com> Cc: Al Viro <viro@zeniv.linux.org.uk> Cc: Alan Cox <alan@lxorguk.ukuu.org.uk> Cc: Paul Fulghum <paulkf@microgate.com> Cc: Casey Schaufler <casey@schaufler-ca.com> Cc: Steve Grubb <sgrubb@redhat.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
* [PATCH] audit signal recipientsAmy Griffis2007-05-11
| | | | | | | | | | | | | When auditing syscalls that send signals, log the pid and security context for each target process. Optimize the data collection by adding a counter for signal-related rules, and avoiding allocating an aux struct unless we have more than one target process. For process groups, collect pid/context data in blocks of 16. Move the audit_signal_info() hook up in check_kill_permission() so we audit attempts where permission is denied. Signed-off-by: Amy Griffis <amy.griffis@hp.com> Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
* [PATCH] audit: AUDIT_PERM supportAl Viro2006-09-11
| | | | | | add support for AUDIT_PERM predicate Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
* [PATCH] add rule filterkeyAmy Griffis2006-07-01
| | | | | | | | | | | | | | | | | Add support for a rule key, which can be used to tie audit records to audit rules. This is useful when a watched file is accessed through a link or symlink, as well as for general audit log analysis. Because this patch uses a string key instead of an integer key, there is a bit of extra overhead to do the kstrdup() when a rule fires. However, we're also allocating memory for the audit record buffer, so it's probably not that significant. I went ahead with a string key because it seems more user-friendly. Note that the user must ensure that filterkeys are unique. The kernel only checks for duplicate rules. Signed-off-by: Amy Griffis <amy.griffis@hpd.com>
* [PATCH] log more info for directory entry change eventsAmy Griffis2006-06-20
| | | | | | | | | | | | | | | | | | | | | | | | | When an audit event involves changes to a directory entry, include a PATH record for the directory itself. A few other notable changes: - fixed audit_inode_child() hooks in fsnotify_move() - removed unused flags arg from audit_inode() - added audit log routines for logging a portion of a string Here's some sample output. before patch: type=SYSCALL msg=audit(1149821605.320:26): arch=40000003 syscall=39 success=yes exit=0 a0=bf8d3c7c a1=1ff a2=804e1b8 a3=bf8d3c7c items=1 ppid=739 pid=800 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=ttyS0 comm="mkdir" exe="/bin/mkdir" subj=root:system_r:unconfined_t:s0-s0:c0.c255 type=CWD msg=audit(1149821605.320:26): cwd="/root" type=PATH msg=audit(1149821605.320:26): item=0 name="foo" parent=164068 inode=164010 dev=03:00 mode=040755 ouid=0 ogid=0 rdev=00:00 obj=root:object_r:user_home_t:s0 after patch: type=SYSCALL msg=audit(1149822032.332:24): arch=40000003 syscall=39 success=yes exit=0 a0=bfdd9c7c a1=1ff a2=804e1b8 a3=bfdd9c7c items=2 ppid=714 pid=777 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=ttyS0 comm="mkdir" exe="/bin/mkdir" subj=root:system_r:unconfined_t:s0-s0:c0.c255 type=CWD msg=audit(1149822032.332:24): cwd="/root" type=PATH msg=audit(1149822032.332:24): item=0 name="/root" inode=164068 dev=03:00 mode=040750 ouid=0 ogid=0 rdev=00:00 obj=root:object_r:user_home_dir_t:s0 type=PATH msg=audit(1149822032.332:24): item=1 name="foo" inode=164010 dev=03:00 mode=040755 ouid=0 ogid=0 rdev=00:00 obj=root:object_r:user_home_t:s0 Signed-off-by: Amy Griffis <amy.griffis@hp.com> Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
* [PATCH] audit: path-based rulesAmy Griffis2006-06-20
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | In this implementation, audit registers inotify watches on the parent directories of paths specified in audit rules. When audit's inotify event handler is called, it updates any affected rules based on the filesystem event. If the parent directory is renamed, removed, or its filesystem is unmounted, audit removes all rules referencing that inotify watch. To keep things simple, this implementation limits location-based auditing to the directory entries in an existing directory. Given a path-based rule for /foo/bar/passwd, the following table applies: passwd modified -- audit event logged passwd replaced -- audit event logged, rules list updated bar renamed -- rule removed foo renamed -- untracked, meaning that the rule now applies to the new location Audit users typically want to have many rules referencing filesystem objects, which can significantly impact filtering performance. This patch also adds an inode-number-based rule hash to mitigate this situation. The patch is relative to the audit git tree: http://kernel.org/git/?p=linux/kernel/git/viro/audit-current.git;a=summary and uses the inotify kernel API: http://lkml.org/lkml/2006/6/1/145 Signed-off-by: Amy Griffis <amy.griffis@hp.com> Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
* [PATCH] collect sid of those who send signals to auditdAl Viro2006-06-20
| | | | Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
* [PATCH] fix deadlocks in AUDIT_LIST/AUDIT_LIST_RULESAl Viro2006-06-20
| | | | | | | | | | | | | | | | We should not send a pile of replies while holding audit_netlink_mutex since we hold the same mutex when we receive commands. As the result, we can get blocked while sending and sit there holding the mutex while auditctl is unable to send the next command and get around to receiving what we'd sent. Solution: create skb and put them into a queue instead of sending; once we are done, send what we've got on the list. The former can be done synchronously while we are handling AUDIT_LIST or AUDIT_LIST_RULES; we are holding audit_netlink_mutex at that point. The latter is done asynchronously and without messing with audit_netlink_mutex. Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
* [PATCH] support for context based audit filtering, part 2Darrel Goeddel2006-05-01
| | | | | | | | | | | This patch provides the ability to filter audit messages based on the elements of the process' SELinux context (user, role, type, mls sensitivity, and mls clearance). It uses the new interfaces from selinux to opaquely store information related to the selinux context and to filter based on that information. It also uses the callback mechanism provided by selinux to refresh the information when a new policy is loaded. Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
* [PATCH] sem2mutex: audit_netlink_semIngo Molnar2006-03-20
| | | | | | | | | | | | Semaphore to mutex conversion. The conversion was generated via scripts, and the result was validated automatically via a script as well. Signed-off-by: Ingo Molnar <mingo@elte.hu> Cc: David Woodhouse <dwmw2@infradead.org> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
* [PATCH] audit string fields interface + consumerAmy Griffis2006-03-20
| | | | | | | | | | | | | | | Updated patch to dynamically allocate audit rule fields in kernel's internal representation. Added unlikely() calls for testing memory allocation result. Amy Griffis wrote: [Wed Jan 11 2006, 02:02:31PM EST] > Modify audit's kernel-userspace interface to allow the specification > of string fields in audit rules. > > Signed-off-by: Amy Griffis <amy.griffis@hp.com> Signed-off-by: Al Viro <viro@zeniv.linux.org.uk> (cherry picked from 5ffc4a863f92351b720fe3e9c5cd647accff9e03 commit)
* [PATCH] Fix audit record filtering with !CONFIG_AUDITSYSCALLDavid Woodhouse2006-03-20
This fixes the per-user and per-message-type filtering when syscall auditing isn't enabled. [AV: folded followup fix from the same author] Signed-off-by: David Woodhouse <dwmw2@infradead.org> Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
8' href='#n858'>858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091
/*     
 *   ALSA Driver for Ego Systems Inc. (ESI) Miditerminal 4140
 *   Copyright (c) 2006 by Matthias König <mk@phasorlab.de>
 *
 *   This program is free software; you can redistribute it and/or modify 
 *   it under the terms of the GNU General Public License as published by 
 *   the Free Software Foundation; either version 2 of the License, or 
 *   (at your option) any later version. 
 *
 *   This program is distributed in the hope that it will be useful, 
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of 
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#include <sound/driver.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/parport.h>
#include <linux/spinlock.h>
#include <linux/delay.h>
#include <sound/core.h>
#include <sound/initval.h>
#include <sound/rawmidi.h>
#include <sound/control.h>

#define CARD_NAME "Miditerminal 4140"
#define DRIVER_NAME "MTS64"
#define PLATFORM_DRIVER "snd_mts64"

static int index[SNDRV_CARDS]  = SNDRV_DEFAULT_IDX;
static char *id[SNDRV_CARDS]   = SNDRV_DEFAULT_STR;
static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;

static struct platform_device *platform_devices[SNDRV_CARDS]; 
static int device_count;

module_param_array(index, int, NULL, S_IRUGO);
MODULE_PARM_DESC(index, "Index value for " CARD_NAME " soundcard.");
module_param_array(id, charp, NULL, S_IRUGO);
MODULE_PARM_DESC(id, "ID string for " CARD_NAME " soundcard.");
module_param_array(enable, bool, NULL, S_IRUGO);
MODULE_PARM_DESC(enable, "Enable " CARD_NAME " soundcard.");

MODULE_AUTHOR("Matthias Koenig <mk@phasorlab.de>");
MODULE_DESCRIPTION("ESI Miditerminal 4140");
MODULE_LICENSE("GPL");
MODULE_SUPPORTED_DEVICE("{{ESI,Miditerminal 4140}}");

/*********************************************************************
 * Chip specific
 *********************************************************************/
#define MTS64_NUM_INPUT_PORTS 5
#define MTS64_NUM_OUTPUT_PORTS 4
#define MTS64_SMPTE_SUBSTREAM 4

struct mts64 {
	spinlock_t lock;
	struct snd_card *card;
	struct snd_rawmidi *rmidi;
	struct pardevice *pardev;
	int pardev_claimed;

	int open_count;
	int current_midi_output_port;
	int current_midi_input_port;
	u8 mode[MTS64_NUM_INPUT_PORTS];
	struct snd_rawmidi_substream *midi_input_substream[MTS64_NUM_INPUT_PORTS];
	int smpte_switch;
	u8 time[4]; /* [0]=hh, [1]=mm, [2]=ss, [3]=ff */
	u8 fps;
};

static int snd_mts64_free(struct mts64 *mts)
{
	kfree(mts);
	return 0;
}

static int __devinit snd_mts64_create(struct snd_card *card, 
				      struct pardevice *pardev, 
				      struct mts64 **rchip)
{
	struct mts64 *mts;

	*rchip = NULL;

	mts = kzalloc(sizeof(struct mts64), GFP_KERNEL);
	if (mts == NULL) 
		return -ENOMEM;

	/* Init chip specific data */
	spin_lock_init(&mts->lock);
	mts->card = card;
	mts->pardev = pardev;
	mts->current_midi_output_port = -1;
	mts->current_midi_input_port = -1;

	*rchip = mts;

	return 0;
}

/*********************************************************************
 * HW register related constants
 *********************************************************************/

/* Status Bits */
#define MTS64_STAT_BSY             0x80
#define MTS64_STAT_BIT_SET         0x20  /* readout process, bit is set */
#define MTS64_STAT_PORT            0x10  /* read byte is a port number */

/* Control Bits */
#define MTS64_CTL_READOUT          0x08  /* enable readout */
#define MTS64_CTL_WRITE_CMD        0x06  
#define MTS64_CTL_WRITE_DATA       0x02  
#define MTS64_CTL_STROBE           0x01  

/* Command */
#define MTS64_CMD_RESET            0xfe
#define MTS64_CMD_PROBE            0x8f  /* Used in probing procedure */
#define MTS64_CMD_SMPTE_SET_TIME   0xe8
#define MTS64_CMD_SMPTE_SET_FPS    0xee
#define MTS64_CMD_SMPTE_STOP       0xef
#define MTS64_CMD_SMPTE_FPS_24     0xe3
#define MTS64_CMD_SMPTE_FPS_25     0xe2
#define MTS64_CMD_SMPTE_FPS_2997   0xe4 
#define MTS64_CMD_SMPTE_FPS_30D    0xe1
#define MTS64_CMD_SMPTE_FPS_30     0xe0
#define MTS64_CMD_COM_OPEN         0xf8  /* setting the communication mode */
#define MTS64_CMD_COM_CLOSE1       0xff  /* clearing communication mode */
#define MTS64_CMD_COM_CLOSE2       0xf5

/*********************************************************************
 * Hardware specific functions
 *********************************************************************/
static void mts64_enable_readout(struct parport *p);
static void mts64_disable_readout(struct parport *p);
static int mts64_device_ready(struct parport *p);
static int mts64_device_init(struct parport *p);
static int mts64_device_open(struct mts64 *mts);
static int mts64_device_close(struct mts64 *mts);
static u8 mts64_map_midi_input(u8 c);
static int mts64_probe(struct parport *p);
static u16 mts64_read(struct parport *p);
static u8 mts64_read_char(struct parport *p);
static void mts64_smpte_start(struct parport *p,
			      u8 hours, u8 minutes,
			      u8 seconds, u8 frames,
			      u8 idx);
static void mts64_smpte_stop(struct parport *p);
static void mts64_write_command(struct parport *p, u8 c);
static void mts64_write_data(struct parport *p, u8 c);
static void mts64_write_midi(struct mts64 *mts, u8 c, int midiport);


/*  Enables the readout procedure
 *
 *  Before we can read a midi byte from the device, we have to set
 *  bit 3 of control port.
 */
static void mts64_enable_readout(struct parport *p)
{
	u8 c;

	c = parport_read_control(p);
	c |= MTS64_CTL_READOUT;
	parport_write_control(p, c); 
}

/*  Disables readout 
 *
 *  Readout is disabled by clearing bit 3 of control
 */
static void mts64_disable_readout(struct parport *p)
{
	u8 c;

	c = parport_read_control(p);
	c &= ~MTS64_CTL_READOUT;
	parport_write_control(p, c);
}

/*  waits for device ready
 *
 *  Checks if BUSY (Bit 7 of status) is clear
 *  1 device ready
 *  0 failure
 */
static int mts64_device_ready(struct parport *p)
{
	int i;
	u8 c;

	for (i = 0; i < 0xffff; ++i) {
		c = parport_read_status(p);
		c &= MTS64_STAT_BSY;
		if (c != 0) 
			return 1;
	} 

	return 0;
}

/*  Init device (LED blinking startup magic)
 *
 *  Returns:
 *  0 init ok
 *  -EIO failure
 */
static int __devinit mts64_device_init(struct parport *p)
{
	int i;

	mts64_write_command(p, MTS64_CMD_RESET);

	for (i = 0; i < 64; ++i) {
		msleep(100);

		if (mts64_probe(p) == 0) {
			/* success */
			mts64_disable_readout(p);
			return 0;
		}
	}
	mts64_disable_readout(p);

	return -EIO;
}

/* 
 *  Opens the device (set communication mode)
 */
static int mts64_device_open(struct mts64 *mts)
{
	int i;
	struct parport *p = mts->pardev->port;

	for (i = 0; i < 5; ++i)
		mts64_write_command(p, MTS64_CMD_COM_OPEN);

	return 0;
}

/*  
 *  Close device (clear communication mode)
 */
static int mts64_device_close(struct mts64 *mts)
{
	int i;
	struct parport *p = mts->pardev->port;

	for (i = 0; i < 5; ++i) {
		mts64_write_command(p, MTS64_CMD_COM_CLOSE1);
		mts64_write_command(p, MTS64_CMD_COM_CLOSE2);
	}

	return 0;
}

/*  map hardware port to substream number
 * 
 *  When reading a byte from the device, the device tells us
 *  on what port the byte is. This HW port has to be mapped to
 *  the midiport (substream number).
 *  substream 0-3 are Midiports 1-4
 *  substream 4 is SMPTE Timecode
 *  The mapping is done by the table:
 *  HW | 0 | 1 | 2 | 3 | 4 
 *  SW | 0 | 1 | 4 | 2 | 3
 */
static u8 mts64_map_midi_input(u8 c)
{
	static u8 map[] = { 0, 1, 4, 2, 3 };

	return map[c];
}


/*  Probe parport for device
 *
 *  Do we have a Miditerminal 4140 on parport? 
 *  Returns:
 *  0       device found
 *  -ENODEV no device
 */
static int __devinit mts64_probe(struct parport *p)
{
	u8 c;

	mts64_smpte_stop(p);
	mts64_write_command(p, MTS64_CMD_PROBE);

	msleep(50);
	
	c = mts64_read(p);

	c &= 0x00ff;
	if (c != MTS64_CMD_PROBE) 
		return -ENODEV;
	else 
		return 0;

}

/*  Read byte incl. status from device
 *
 *  Returns:
 *  data in lower 8 bits and status in upper 8 bits
 */
static u16 mts64_read(struct parport *p)
{
	u8 data, status;

	mts64_device_ready(p);
	mts64_enable_readout(p);
	status = parport_read_status(p);
	data = mts64_read_char(p);
	mts64_disable_readout(p);

	return (status << 8) | data;
}

/*  Read a byte from device
 *
 *  Note, that readout mode has to be enabled.
 *  readout procedure is as follows: 
 *  - Write number of the Bit to read to DATA
 *  - Read STATUS
 *  - Bit 5 of STATUS indicates if Bit is set
 *
 *  Returns:
 *  Byte read from device
 */
static u8 mts64_read_char(struct parport *p)
{
	u8 c = 0;
	u8 status;
	u8 i;

	for (i = 0; i < 8; ++i) {
		parport_write_data(p, i);
		c >>= 1;
		status = parport_read_status(p);
		if (status & MTS64_STAT_BIT_SET) 
			c |= 0x80;
	}
	
	return c;
}

/*  Starts SMPTE Timecode generation
 *
 *  The device creates SMPTE Timecode by hardware.
 *  0 24 fps
 *  1 25 fps
 *  2 29.97 fps
 *  3 30 fps (Drop-frame)
 *  4 30 fps
 */
static void mts64_smpte_start(struct parport *p,
			      u8 hours, u8 minutes,
			      u8 seconds, u8 frames,
			      u8 idx)
{
	static u8 fps[5] = { MTS64_CMD_SMPTE_FPS_24, 
			     MTS64_CMD_SMPTE_FPS_25,
			     MTS64_CMD_SMPTE_FPS_2997, 
			     MTS64_CMD_SMPTE_FPS_30D,
			     MTS64_CMD_SMPTE_FPS_30    };

	mts64_write_command(p, MTS64_CMD_SMPTE_SET_TIME);
	mts64_write_command(p, frames);
	mts64_write_command(p, seconds);
	mts64_write_command(p, minutes);
	mts64_write_command(p, hours);

	mts64_write_command(p, MTS64_CMD_SMPTE_SET_FPS);
	mts64_write_command(p, fps[idx]);
}

/*  Stops SMPTE Timecode generation
 */
static void mts64_smpte_stop(struct parport *p)
{
	mts64_write_command(p, MTS64_CMD_SMPTE_STOP);
}

/*  Write a command byte to device
 */
static void mts64_write_command(struct parport *p, u8 c)
{
	mts64_device_ready(p);

	parport_write_data(p, c);

	parport_write_control(p, MTS64_CTL_WRITE_CMD);
	parport_write_control(p, MTS64_CTL_WRITE_CMD | MTS64_CTL_STROBE);
	parport_write_control(p, MTS64_CTL_WRITE_CMD);
}

/*  Write a data byte to device 
 */
static void mts64_write_data(struct parport *p, u8 c)
{
	mts64_device_ready(p);

	parport_write_data(p, c);

	parport_write_control(p, MTS64_CTL_WRITE_DATA);
	parport_write_control(p, MTS64_CTL_WRITE_DATA | MTS64_CTL_STROBE);
	parport_write_control(p, MTS64_CTL_WRITE_DATA);
}

/*  Write a MIDI byte to midiport
 *
 *  midiport ranges from 0-3 and maps to Ports 1-4
 *  assumptions: communication mode is on
 */
static void mts64_write_midi(struct mts64 *mts, u8 c,
			     int midiport)
{
	struct parport *p = mts->pardev->port;

	/* check current midiport */
	if (mts->current_midi_output_port != midiport)
		mts64_write_command(p, midiport);

	/* write midi byte */
	mts64_write_data(p, c);
}

/*********************************************************************
 * Control elements
 *********************************************************************/

/* SMPTE Switch */
static int snd_mts64_ctl_smpte_switch_info(struct snd_kcontrol *kctl,
					   struct snd_ctl_elem_info *uinfo)
{
	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
	uinfo->count = 1;
	uinfo->value.integer.min = 0;
	uinfo->value.integer.max = 1;
	return 0;
}

static int snd_mts64_ctl_smpte_switch_get(struct snd_kcontrol* kctl,
					  struct snd_ctl_elem_value *uctl)
{
	struct mts64 *mts = snd_kcontrol_chip(kctl);

	spin_lock_irq(&mts->lock);
	uctl->value.integer.value[0] = mts->smpte_switch;
	spin_unlock_irq(&mts->lock);

	return 0;
}

/* smpte_switch is not accessed from IRQ handler, so we just need
   to protect the HW access */
static int snd_mts64_ctl_smpte_switch_put(struct snd_kcontrol* kctl,
					  struct snd_ctl_elem_value *uctl)
{
	struct mts64 *mts = snd_kcontrol_chip(kctl);
	int changed = 0;

	spin_lock_irq(&mts->lock);
	if (mts->smpte_switch == uctl->value.integer.value[0])
		goto __out;

	changed = 1;
	mts->smpte_switch = uctl->value.integer.value[0];
	if (mts->smpte_switch) {
		mts64_smpte_start(mts->pardev->port,
				  mts->time[0], mts->time[1],
				  mts->time[2], mts->time[3],
				  mts->fps);
	} else {
		mts64_smpte_stop(mts->pardev->port);
	}
__out:
	spin_unlock_irq(&mts->lock);
	return changed;
}

static struct snd_kcontrol_new mts64_ctl_smpte_switch __devinitdata = {
	.iface = SNDRV_CTL_ELEM_IFACE_RAWMIDI,
	.name  = "SMPTE Playback Switch",
	.index = 0,
	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
	.private_value = 0,
	.info = snd_mts64_ctl_smpte_switch_info,
	.get  = snd_mts64_ctl_smpte_switch_get,
	.put  = snd_mts64_ctl_smpte_switch_put
};

/* Time */
static int snd_mts64_ctl_smpte_time_h_info(struct snd_kcontrol *kctl,
					   struct snd_ctl_elem_info *uinfo)
{
	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
	uinfo->count = 1;
	uinfo->value.integer.min = 0;
	uinfo->value.integer.max = 23;
	return 0;
}

static int snd_mts64_ctl_smpte_time_f_info(struct snd_kcontrol *kctl,
					   struct snd_ctl_elem_info *uinfo)
{
	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
	uinfo->count = 1;
	uinfo->value.integer.min = 0;
	uinfo->value.integer.max = 99;
	return 0;
}

static int snd_mts64_ctl_smpte_time_info(struct snd_kcontrol *kctl,
					 struct snd_ctl_elem_info *uinfo)
{
	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
	uinfo->count = 1;
	uinfo->value.integer.min = 0;
	uinfo->value.integer.max = 59;
	return 0;
}

static int snd_mts64_ctl_smpte_time_get(struct snd_kcontrol *kctl,
					struct snd_ctl_elem_value *uctl)
{
	struct mts64 *mts = snd_kcontrol_chip(kctl);
	int idx = kctl->private_value;

	spin_lock_irq(&mts->lock);
	uctl->value.integer.value[0] = mts->time[idx];
	spin_unlock_irq(&mts->lock);

	return 0;
}

static int snd_mts64_ctl_smpte_time_put(struct snd_kcontrol *kctl,
					struct snd_ctl_elem_value *uctl)
{
	struct mts64 *mts = snd_kcontrol_chip(kctl);
	int idx = kctl->private_value;
	int changed = 0;

	spin_lock_irq(&mts->lock);
	if (mts->time[idx] != uctl->value.integer.value[0]) {
		changed = 1;
		mts->time[idx] = uctl->value.integer.value[0];
	}
	spin_unlock_irq(&mts->lock);

	return changed;
}

static struct snd_kcontrol_new mts64_ctl_smpte_time_hours __devinitdata = {
	.iface = SNDRV_CTL_ELEM_IFACE_RAWMIDI,
	.name  = "SMPTE Time Hours",
	.index = 0,
	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
	.private_value = 0,
	.info = snd_mts64_ctl_smpte_time_h_info,
	.get  = snd_mts64_ctl_smpte_time_get,
	.put  = snd_mts64_ctl_smpte_time_put
};

static struct snd_kcontrol_new mts64_ctl_smpte_time_minutes __devinitdata = {
	.iface = SNDRV_CTL_ELEM_IFACE_RAWMIDI,
	.name  = "SMPTE Time Minutes",
	.index = 0,
	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
	.private_value = 1,
	.info = snd_mts64_ctl_smpte_time_info,
	.get  = snd_mts64_ctl_smpte_time_get,
	.put  = snd_mts64_ctl_smpte_time_put
};

static struct snd_kcontrol_new mts64_ctl_smpte_time_seconds __devinitdata = {
	.iface = SNDRV_CTL_ELEM_IFACE_RAWMIDI,
	.name  = "SMPTE Time Seconds",
	.index = 0,
	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
	.private_value = 2,
	.info = snd_mts64_ctl_smpte_time_info,
	.get  = snd_mts64_ctl_smpte_time_get,
	.put  = snd_mts64_ctl_smpte_time_put
};

static struct snd_kcontrol_new mts64_ctl_smpte_time_frames __devinitdata = {
	.iface = SNDRV_CTL_ELEM_IFACE_RAWMIDI,
	.name  = "SMPTE Time Frames",
	.index = 0,
	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
	.private_value = 3,
	.info = snd_mts64_ctl_smpte_time_f_info,
	.get  = snd_mts64_ctl_smpte_time_get,
	.put  = snd_mts64_ctl_smpte_time_put
};

/* FPS */
static int snd_mts64_ctl_smpte_fps_info(struct snd_kcontrol *kctl,
					struct snd_ctl_elem_info *uinfo)
{
	static char *texts[5] = { "24",
				  "25",
				  "29.97",
				  "30D",
				  "30"    };

	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
	uinfo->count = 1;
	uinfo->value.enumerated.items = 5;
	if (uinfo->value.enumerated.item > 4)
		uinfo->value.enumerated.item = 4;
	strcpy(uinfo->value.enumerated.name,
	       texts[uinfo->value.enumerated.item]);
	
	return 0;
}

static int snd_mts64_ctl_smpte_fps_get(struct snd_kcontrol *kctl,
				       struct snd_ctl_elem_value *uctl)
{
	struct mts64 *mts = snd_kcontrol_chip(kctl);

	spin_lock_irq(&mts->lock);
	uctl->value.enumerated.item[0] = mts->fps;
	spin_unlock_irq(&mts->lock);

	return 0;
}

static int snd_mts64_ctl_smpte_fps_put(struct snd_kcontrol *kctl,
				       struct snd_ctl_elem_value *uctl)
{
	struct mts64 *mts = snd_kcontrol_chip(kctl);
	int changed = 0;

	spin_lock_irq(&mts->lock);
	if (mts->fps != uctl->value.enumerated.item[0]) {
		changed = 1;
		mts->fps = uctl->value.enumerated.item[0];
	}
	spin_unlock_irq(&mts->lock);

	return changed;
}

static struct snd_kcontrol_new mts64_ctl_smpte_fps __devinitdata = {
	.iface = SNDRV_CTL_ELEM_IFACE_RAWMIDI,
	.name  = "SMPTE Fps",
	.index = 0,
	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
	.private_value = 0,
	.info  = snd_mts64_ctl_smpte_fps_info,
	.get   = snd_mts64_ctl_smpte_fps_get,
	.put   = snd_mts64_ctl_smpte_fps_put
};


static int __devinit snd_mts64_ctl_create(struct snd_card *card, 
					  struct mts64 *mts) 
{
	int err, i;
	static struct snd_kcontrol_new *control[] = {
		&mts64_ctl_smpte_switch,
		&mts64_ctl_smpte_time_hours,
		&mts64_ctl_smpte_time_minutes,
		&mts64_ctl_smpte_time_seconds,
		&mts64_ctl_smpte_time_frames,
		&mts64_ctl_smpte_fps,
	        NULL  };

	for (i = 0; control[i]; ++i) {
		err = snd_ctl_add(card, snd_ctl_new1(control[i], mts));
		if (err < 0) {
			snd_printd("Cannot create control: %s\n", 
				   control[i]->name);
			return err;
		}
	}

	return 0;
}

/*********************************************************************
 * Rawmidi
 *********************************************************************/
#define MTS64_MODE_INPUT_TRIGGERED 0x01

static int snd_mts64_rawmidi_open(struct snd_rawmidi_substream *substream)
{
	struct mts64 *mts = substream->rmidi->private_data;

	if (mts->open_count == 0) {
		/* We don't need a spinlock here, because this is just called 
		   if the device has not been opened before. 
		   So there aren't any IRQs from the device */
		mts64_device_open(mts);

		msleep(50);
	}
	++(mts->open_count);

	return 0;
}

static int snd_mts64_rawmidi_close(struct snd_rawmidi_substream *substream)
{
	struct mts64 *mts = substream->rmidi->private_data;
	unsigned long flags;

	--(mts->open_count);
	if (mts->open_count == 0) {
		/* We need the spinlock_irqsave here because we can still
		   have IRQs at this point */
		spin_lock_irqsave(&mts->lock, flags);
		mts64_device_close(mts);
		spin_unlock_irqrestore(&mts->lock, flags);

		msleep(500);

	} else if (mts->open_count < 0)
		mts->open_count = 0;

	return 0;
}

static void snd_mts64_rawmidi_output_trigger(struct snd_rawmidi_substream *substream,
					     int up)
{
	struct mts64 *mts = substream->rmidi->private_data;
	u8 data;
	unsigned long flags;

	spin_lock_irqsave(&mts->lock, flags);
	while (snd_rawmidi_transmit_peek(substream, &data, 1) == 1) {
		mts64_write_midi(mts, data, substream->number+1);
		snd_rawmidi_transmit_ack(substream, 1);
	}
	spin_unlock_irqrestore(&mts->lock, flags);
}

static void snd_mts64_rawmidi_input_trigger(struct snd_rawmidi_substream *substream,
					    int up)
{
	struct mts64 *mts = substream->rmidi->private_data;
	unsigned long flags;

	spin_lock_irqsave(&mts->lock, flags);
	if (up)
		mts->mode[substream->number] |= MTS64_MODE_INPUT_TRIGGERED;
	else
 		mts->mode[substream->number] &= ~MTS64_MODE_INPUT_TRIGGERED;
	
	spin_unlock_irqrestore(&mts->lock, flags);
}

static struct snd_rawmidi_ops snd_mts64_rawmidi_output_ops = {
	.open    = snd_mts64_rawmidi_open,
	.close   = snd_mts64_rawmidi_close,
	.trigger = snd_mts64_rawmidi_output_trigger
};

static struct snd_rawmidi_ops snd_mts64_rawmidi_input_ops = {
	.open    = snd_mts64_rawmidi_open,
	.close   = snd_mts64_rawmidi_close,
	.trigger = snd_mts64_rawmidi_input_trigger
};

/* Create and initialize the rawmidi component */
static int __devinit snd_mts64_rawmidi_create(struct snd_card *card)
{
	struct mts64 *mts = card->private_data;
	struct snd_rawmidi *rmidi;
	struct snd_rawmidi_substream *substream;
	struct list_head *list;
	int err;
	
	err = snd_rawmidi_new(card, CARD_NAME, 0, 
			      MTS64_NUM_OUTPUT_PORTS, 
			      MTS64_NUM_INPUT_PORTS, 
			      &rmidi);
	if (err < 0) 
		return err;

	rmidi->private_data = mts;
	strcpy(rmidi->name, CARD_NAME);
	rmidi->info_flags = SNDRV_RAWMIDI_INFO_OUTPUT |
		            SNDRV_RAWMIDI_INFO_INPUT |
                            SNDRV_RAWMIDI_INFO_DUPLEX;

	mts->rmidi = rmidi;

	/* register rawmidi ops */
	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, 
			    &snd_mts64_rawmidi_output_ops);
	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, 
			    &snd_mts64_rawmidi_input_ops);

	/* name substreams */
	/* output */
	list_for_each(list, 
		      &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT].substreams) {
		substream = list_entry(list, struct snd_rawmidi_substream, list);
		sprintf(substream->name,
			"Miditerminal %d", substream->number+1);
	}
	/* input */
	list_for_each(list, 
		      &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT].substreams) {
		substream = list_entry(list, struct snd_rawmidi_substream, list);
		mts->midi_input_substream[substream->number] = substream;
		switch(substream->number) {
		case MTS64_SMPTE_SUBSTREAM:
			strcpy(substream->name, "Miditerminal SMPTE");
			break;
		default:
			sprintf(substream->name,
				"Miditerminal %d", substream->number+1);
		}
	}

	/* controls */
	err = snd_mts64_ctl_create(card, mts);

	return err;
}

/*********************************************************************
 * parport stuff
 *********************************************************************/
static void snd_mts64_interrupt(int irq, void *private)
{
	struct mts64 *mts = ((struct snd_card*)private)->private_data;
	u16 ret;
	u8 status, data;
	struct snd_rawmidi_substream *substream;

	spin_lock(&mts->lock);
	ret = mts64_read(mts->pardev->port);
	data = ret & 0x00ff;
	status = ret >> 8;

	if (status & MTS64_STAT_PORT) {
		mts->current_midi_input_port = mts64_map_midi_input(data);
	} else {
		if (mts->current_midi_input_port == -1) 
			goto __out;
		substream = mts->midi_input_substream[mts->current_midi_input_port];
		if (mts->mode[substream->number] & MTS64_MODE_INPUT_TRIGGERED)
			snd_rawmidi_receive(substream, &data, 1);
	}
__out:
	spin_unlock(&mts->lock);
}

static int __devinit snd_mts64_probe_port(struct parport *p)
{
	struct pardevice *pardev;
	int res;

	pardev = parport_register_device(p, DRIVER_NAME,
					 NULL, NULL, NULL,
					 0, NULL);
	if (!pardev)
		return -EIO;
	
	if (parport_claim(pardev)) {
		parport_unregister_device(pardev);
		return -EIO;
	}

	res = mts64_probe(p);

	parport_release(pardev);
	parport_unregister_device(pardev);

	return res;
}

static void __devinit snd_mts64_attach(struct parport *p)
{
	struct platform_device *device;

	device = platform_device_alloc(PLATFORM_DRIVER, device_count);
	if (!device) 
		return;

	/* Temporary assignment to forward the parport */
	platform_set_drvdata(device, p);

	if (platform_device_register(device) < 0) {
		platform_device_put(device);
		return;
	}

	/* Since we dont get the return value of probe
	 * We need to check if device probing succeeded or not */
	if (!platform_get_drvdata(device)) {
		platform_device_unregister(device);
		return;
	}

	/* register device in global table */
	platform_devices[device_count] = device;
	device_count++;
}

static void snd_mts64_detach(struct parport *p)
{
	/* nothing to do here */
}

static struct parport_driver mts64_parport_driver = {
	.name   = "mts64",
	.attach = snd_mts64_attach,
	.detach = snd_mts64_detach
};

/*********************************************************************
 * platform stuff
 *********************************************************************/
static void snd_mts64_card_private_free(struct snd_card *card)
{
	struct mts64 *mts = card->private_data;
	struct pardevice *pardev = mts->pardev;

	if (pardev) {
		if (mts->pardev_claimed)
			parport_release(pardev);
		parport_unregister_device(pardev);
	}

	snd_mts64_free(mts);
}

static int __devinit snd_mts64_probe(struct platform_device *pdev)
{
	struct pardevice *pardev;
	struct parport *p;
	int dev = pdev->id;
	struct snd_card *card = NULL;
	struct mts64 *mts = NULL;
	int err;

	p = platform_get_drvdata(pdev);
	platform_set_drvdata(pdev, NULL);

	if (dev >= SNDRV_CARDS)
		return -ENODEV;
	if (!enable[dev]) 
		return -ENOENT;
	if ((err = snd_mts64_probe_port(p)) < 0)
		return err;

	card = snd_card_new(index[dev], id[dev], THIS_MODULE, 0);
	if (card == NULL) {
		snd_printd("Cannot create card\n");
		return -ENOMEM;
	}
	strcpy(card->driver, DRIVER_NAME);
	strcpy(card->shortname, "ESI " CARD_NAME);
	sprintf(card->longname,  "%s at 0x%lx, irq %i", 
		card->shortname, p->base, p->irq);

	pardev = parport_register_device(p,                   /* port */
					 DRIVER_NAME,         /* name */
					 NULL,                /* preempt */
					 NULL,                /* wakeup */
					 snd_mts64_interrupt, /* ISR */
					 PARPORT_DEV_EXCL,    /* flags */
					 (void *)card);       /* private */
	if (pardev == NULL) {
		snd_printd("Cannot register pardevice\n");
		err = -EIO;
		goto __err;
	}

	if ((err = snd_mts64_create(card, pardev, &mts)) < 0) {
		snd_printd("Cannot create main component\n");
		parport_unregister_device(pardev);
		goto __err;
	}
	card->private_data = mts;
	card->private_free = snd_mts64_card_private_free;
	
	if ((err = snd_mts64_rawmidi_create(card)) < 0) {
		snd_printd("Creating Rawmidi component failed\n");
		goto __err;
	}

	/* claim parport */
	if (parport_claim(pardev)) {
		snd_printd("Cannot claim parport 0x%lx\n", pardev->port->base);
		err = -EIO;
		goto __err;
	}
	mts->pardev_claimed = 1;

	/* init device */
	if ((err = mts64_device_init(p)) < 0)
		goto __err;

	platform_set_drvdata(pdev, card);

	/* At this point card will be usable */
	if ((err = snd_card_register(card)) < 0) {
		snd_printd("Cannot register card\n");
		goto __err;
	}

	snd_printk("ESI Miditerminal 4140 on 0x%lx\n", p->base);
	return 0;

__err:
	snd_card_free(card);
	return err;
}

static int __devexit snd_mts64_remove(struct platform_device *pdev)
{
	struct snd_card *card = platform_get_drvdata(pdev);

	if (card)
		snd_card_free(card);

	return 0;
}


static struct platform_driver snd_mts64_driver = {
	.probe  = snd_mts64_probe,
	.remove = __devexit_p(snd_mts64_remove),
	.driver = {
		.name = PLATFORM_DRIVER
	}
};

/*********************************************************************
 * module init stuff
 *********************************************************************/
static void __init_or_module snd_mts64_unregister_all(void)
{
	int i;

	for (i = 0; i < SNDRV_CARDS; ++i) {
		if (platform_devices[i]) {
			platform_device_unregister(platform_devices[i]);
			platform_devices[i] = NULL;
		}
	}		
	platform_driver_unregister(&snd_mts64_driver);
	parport_unregister_driver(&mts64_parport_driver);
}

static int __init snd_mts64_module_init(void)
{
	int err;

	if ((err = platform_driver_register(&snd_mts64_driver)) < 0)
		return err;

	if (parport_register_driver(&mts64_parport_driver) != 0) {
		platform_driver_unregister(&snd_mts64_driver);
		return -EIO;
	}

	if (device_count == 0) {
		snd_mts64_unregister_all();
		return -ENODEV;
	}

	return 0;
}

static void __exit snd_mts64_module_exit(void)
{
	snd_mts64_unregister_all();
}

module_init(snd_mts64_module_init);
module_exit(snd_mts64_module_exit);