tpm: Propagate error from tpm_transmit to fix a timeout hang
[pandora-kernel.git] / drivers / char / tpm / tpm.c
index 9ca5c02..0d91655 100644 (file)
@@ -966,6 +966,9 @@ ssize_t tpm_show_durations(struct device *dev, struct device_attribute *attr,
 {
        struct tpm_chip *chip = dev_get_drvdata(dev);
 
+       if (chip->vendor.duration[TPM_LONG] == 0)
+               return 0;
+
        return sprintf(buf, "%d %d %d [%s]\n",
                       jiffies_to_usecs(chip->vendor.duration[TPM_SHORT]),
                       jiffies_to_usecs(chip->vendor.duration[TPM_MEDIUM]),
@@ -1069,17 +1072,20 @@ ssize_t tpm_write(struct file *file, const char __user *buf,
                  size_t size, loff_t *off)
 {
        struct tpm_chip *chip = file->private_data;
-       size_t in_size = size, out_size;
+       size_t in_size = size;
+       ssize_t out_size;
 
        /* cannot perform a write until the read has cleared
-          either via tpm_read or a user_read_timer timeout */
-       while (atomic_read(&chip->data_pending) != 0)
-               msleep(TPM_TIMEOUT);
-
-       mutex_lock(&chip->buffer_mutex);
+          either via tpm_read or a user_read_timer timeout.
+          This also prevents splitted buffered writes from blocking here.
+       */
+       if (atomic_read(&chip->data_pending) != 0)
+               return -EBUSY;
 
        if (in_size > TPM_BUFSIZE)
-               in_size = TPM_BUFSIZE;
+               return -E2BIG;
+
+       mutex_lock(&chip->buffer_mutex);
 
        if (copy_from_user
            (chip->data_buffer, (void __user *) buf, in_size)) {
@@ -1089,6 +1095,10 @@ ssize_t tpm_write(struct file *file, const char __user *buf,
 
        /* atomic tpm command send and result receive */
        out_size = tpm_transmit(chip, chip->data_buffer, TPM_BUFSIZE);
+       if (out_size < 0) {
+               mutex_unlock(&chip->buffer_mutex);
+               return out_size;
+       }
 
        atomic_set(&chip->data_pending, out_size);
        mutex_unlock(&chip->buffer_mutex);
@@ -1112,12 +1122,13 @@ ssize_t tpm_read(struct file *file, char __user *buf,
        ret_size = atomic_read(&chip->data_pending);
        atomic_set(&chip->data_pending, 0);
        if (ret_size > 0) {     /* relay data */
+               ssize_t orig_ret_size = ret_size;
                if (size < ret_size)
                        ret_size = size;
 
                mutex_lock(&chip->buffer_mutex);
                rc = copy_to_user(buf, chip->data_buffer, ret_size);
-               memset(chip->data_buffer, 0, ret_size);
+               memset(chip->data_buffer, 0, orig_ret_size);
                if (rc)
                        ret_size = -EFAULT;