summaryrefslogtreecommitdiffstats
path: root/sys/dev/amr
diff options
context:
space:
mode:
authorjhb <jhb@FreeBSD.org>2012-04-20 20:27:31 +0000
committerjhb <jhb@FreeBSD.org>2012-04-20 20:27:31 +0000
commit000bbf07daf300341bc3a642e10cf636932d4fcf (patch)
tree7356bbc37f3f3e69a43399ca485c6f667f865062 /sys/dev/amr
parentaa85973504e8ab2a6d5b1ab434c06030e5302f7c (diff)
downloadFreeBSD-src-000bbf07daf300341bc3a642e10cf636932d4fcf.zip
FreeBSD-src-000bbf07daf300341bc3a642e10cf636932d4fcf.tar.gz
The amr(4) firmware contains a rather dubious "feature" where it
assumes for small buffers (< 64k) that the OS driver is actually using a buffer rounded up to the next power of 2. It also assumes that the buffer is at least 4k in size. Furthermore, there is at least one known instance of megarc sending a request with a 12k buffer where the firmware writes out a 24k-ish reply. To workaround the data corruption triggered by this "feature", ensure that buffers for user commands use a minimum size of 32k, and that buffers between 32k and 64k use a 64k buffer. PR: kern/155658 Submitted by: Andreas Longwitz longwitz incore de Reviewed by: scottl MFC after: 1 week
Diffstat (limited to 'sys/dev/amr')
-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