summaryrefslogtreecommitdiffstats
path: root/sys/dev
diff options
context:
space:
mode:
Diffstat (limited to 'sys/dev')
-rw-r--r--sys/dev/amr/amr.c45
1 files changed, 31 insertions, 14 deletions
diff --git a/sys/dev/amr/amr.c b/sys/dev/amr/amr.c
index 4cb42e2..bbcded0 100644
--- a/sys/dev/amr/amr.c
+++ b/sys/dev/amr/amr.c
@@ -535,6 +535,31 @@ shutdown_out:
amr_startup(sc);
}
+/*
+ * Bug-for-bug compatibility with Linux!
+ * Some apps will send commands with inlen and outlen set to 0,
+ * even though they expect data to be transfered to them from the
+ * card. Linux accidentally allows this by allocating a 4KB
+ * buffer for the transfer anyways, but it then throws it away
+ * without copying it back to the app.
+ *
+ * The amr(4) firmware relies on this feature. In fact, it assumes
+ * the buffer is always a power of 2 up to a max of 64k. There is
+ * also at least one case where it assumes a buffer less than 16k is
+ * greater than 16k. Force a minimum buffer size of 32k and round
+ * sizes between 32k and 64k up to 64k as a workaround.
+ */
+static unsigned long
+amr_ioctl_buffer_length(unsigned long len)
+{
+
+ if (len <= 32 * 1024)
+ return (32 * 1024);
+ if (len <= 64 * 1024)
+ return (64 * 1024);
+ return (len);
+}
+
int
amr_linux_ioctl_int(struct cdev *dev, u_long cmd, caddr_t addr, int32_t flag,
struct thread *td)
@@ -664,16 +689,7 @@ amr_linux_ioctl_int(struct cdev *dev, u_long cmd, caddr_t addr, int32_t flag,
error = ENOIOCTL;
break;
} else {
- /*
- * Bug-for-bug compatibility with Linux!
- * Some apps will send commands with inlen and outlen set to 0,
- * even though they expect data to be transfered to them from the
- * card. Linux accidentally allows this by allocating a 4KB
- * buffer for the transfer anyways, but it then throws it away
- * without copying it back to the app.
- */
- if (!len)
- len = 4096;
+ len = amr_ioctl_buffer_length(imax(ali.inlen, ali.outlen));
dp = malloc(len, M_AMR, M_WAITOK | M_ZERO);
@@ -703,7 +719,7 @@ amr_linux_ioctl_int(struct cdev *dev, u_long cmd, caddr_t addr, int32_t flag,
status = ac->ac_status;
error = copyout(&status, &((struct amr_mailbox *)&((struct amr_linux_ioctl *)addr)->mbox[0])->mb_status, sizeof(status));
if (ali.outlen) {
- error = copyout(dp, (void *)(uintptr_t)mb->mb_physaddr, len);
+ error = copyout(dp, (void *)(uintptr_t)mb->mb_physaddr, ali.outlen);
if (error)
break;
}
@@ -750,7 +766,7 @@ amr_ioctl(struct cdev *dev, u_long cmd, caddr_t addr, int32_t flag, struct threa
struct amr_command *ac;
struct amr_mailbox_ioctl *mbi;
void *dp, *au_buffer;
- unsigned long au_length;
+ unsigned long au_length, real_length;
unsigned char *au_cmd;
int *au_statusp, au_direction;
int error;
@@ -842,8 +858,9 @@ amr_ioctl(struct cdev *dev, u_long cmd, caddr_t addr, int32_t flag, struct threa
}
/* handle inbound data buffer */
+ real_length = amr_ioctl_buffer_length(au_length);
if (au_length != 0 && au_cmd[0] != 0x06) {
- if ((dp = malloc(au_length, M_AMR, M_WAITOK|M_ZERO)) == NULL) {
+ if ((dp = malloc(real_length, M_AMR, M_WAITOK|M_ZERO)) == NULL) {
error = ENOMEM;
goto out;
}
@@ -902,7 +919,7 @@ amr_ioctl(struct cdev *dev, u_long cmd, caddr_t addr, int32_t flag, struct threa
/* build the command */
ac->ac_data = dp;
- ac->ac_length = au_length;
+ ac->ac_length = real_length;
ac->ac_flags |= AMR_CMD_DATAIN|AMR_CMD_DATAOUT;
/* run the command */
OpenPOWER on IntegriCloud