Skip to contentSkip to conversation

TPM: Let the tpm char device be openable multiple times

PATCH In review Jason Gunthorpe 4 participants 10 messages Sep 30, 2012 – Oct 15, 2012 TPM device driver

lore
Share & tools
lore
#0 lore raw

PATCH 1/1 TPM: Let the tpm char device be openable multiple times

How to use the TPM is really a user space policy choice, if the environment wants to use middleware then fine, but it is possible to make correct TPM apps without using middleware.

So, remove the kernel restriction that only one process may open the TPM.
- TPM low level functions (in kernel users) are already locked proprely and can run in parallel with the user space interface anyhow.
- Move the user space data buffer and related goop into a struct tpm_file, create one struct tpm_file per open file.

---
 drivers/char/tpm/tpm.c |   97 +++++++++++++++++++++---------------------------
 drivers/char/tpm/tpm.h |   23 ++++++-----
 2 files changed, 55 insertions(+), 65 deletions(-)

This is rebase, retest, resend of a patch I sent two years ago. The discussion on that earlier patch fizzled out. Resending incase there is renewed interest :)

drivers/char/tpm/tpm.c +42 -55
index 60e8442..a161be9 100644
@@ -33,7 +33,6 @@
 
 enum tpm_const {
 	TPM_MINOR = 224,	/* officially assigned */
-	TPM_BUFSIZE = 4096,
 	TPM_NUM_DEVICES = 256,
 };
 
@@ -333,19 +332,19 @@ static const u8 tpm_ordinal_duration[TPM_MAX_ORDINAL] = {
 
 static void user_reader_timeout(unsigned long ptr)
 {
-	struct tpm_chip *chip = (struct tpm_chip *) ptr;
+	struct tpm_file *fl = (struct tpm_file *) ptr;
 
-	schedule_work(&chip->work);
+	schedule_work(&fl->work);
 }
 
 static void timeout_work(struct work_struct *work)
 {
-	struct tpm_chip *chip = container_of(work, struct tpm_chip, work);
+	struct tpm_file *fl = container_of(work, struct tpm_file, work);
 
-	mutex_lock(&chip->buffer_mutex);
-	atomic_set(&chip->data_pending, 0);
-	memset(chip->data_buffer, 0, TPM_BUFSIZE);
-	mutex_unlock(&chip->buffer_mutex);
+	mutex_lock(&fl->buffer_mutex);
+	atomic_set(&fl->data_pending, 0);
+	memset(fl->data_bufferx, 0, sizeof(fl->data_bufferx));
+	mutex_unlock(&fl->buffer_mutex);
 }
 
 /*
@@ -384,9 +383,6 @@ static ssize_t tpm_transmit(struct tpm_chip *chip, const char *buf,
 	u32 count, ordinal;
 	unsigned long stop;
 
-	if (bufsiz > TPM_BUFSIZE)
-		bufsiz = TPM_BUFSIZE;
-
 	count = be32_to_cpu(*((__be32 *) (buf + 2)));
 	ordinal = be32_to_cpu(*((__be32 *) (buf + 6)));
 	if (count == 0)
@@ -1161,6 +1157,7 @@ int tpm_open(struct inode *inode, struct file *file)
 {
 	int minor = iminor(inode);
 	struct tpm_chip *chip = NULL, *pos;
+	struct tpm_file *fl;
 
 	rcu_read_lock();
 	list_for_each_entry_rcu(pos, &tpm_chip_list, list) {
@@ -1175,22 +1172,19 @@ int tpm_open(struct inode *inode, struct file *file)
 	if (!chip)
 		return -ENODEV;
 
-	if (test_and_set_bit(0, &chip->is_open)) {
-		dev_dbg(chip->dev, "Another process owns this TPM\n");
-		put_device(chip->dev);
-		return -EBUSY;
-	}
-
-	chip->data_buffer = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
-	if (chip->data_buffer == NULL) {
-		clear_bit(0, &chip->is_open);
+	fl = kzalloc(sizeof(*fl), GFP_KERNEL);
+	if (fl == NULL) {
 		put_device(chip->dev);
 		return -ENOMEM;
 	}
 
-	atomic_set(&chip->data_pending, 0);
+	fl->chip = chip;
+	mutex_init(&fl->buffer_mutex);
+	setup_timer(&fl->user_read_timer, user_reader_timeout,
+			(unsigned long)fl);
+	INIT_WORK(&fl->work, timeout_work);
 
-	file->private_data = chip;
+	file->private_data = fl;
 	return 0;
 }
 EXPORT_SYMBOL_GPL(tpm_open);
@@ -1200,14 +1194,14 @@ EXPORT_SYMBOL_GPL(tpm_open);
  */
 int tpm_release(struct inode *inode, struct file *file)
 {
-	struct tpm_chip *chip = file->private_data;
+	struct tpm_file *fl = file->private_data;
+	struct tpm_chip *chip = fl->chip;
 
-	del_singleshot_timer_sync(&chip->user_read_timer);
-	flush_work_sync(&chip->work);
+	del_singleshot_timer_sync(&fl->user_read_timer);
+	flush_work_sync(&fl->work);
+	mutex_destroy(&fl->buffer_mutex);
+	kfree(file->private_data);
 	file->private_data = NULL;
-	atomic_set(&chip->data_pending, 0);
-	kfree(chip->data_buffer);
-	clear_bit(0, &chip->is_open);
 	put_device(chip->dev);
 	return 0;
 }
@@ -1216,33 +1210,33 @@ EXPORT_SYMBOL_GPL(tpm_release);
 ssize_t tpm_write(struct file *file, const char __user *buf,
 		  size_t size, loff_t *off)
 {
-	struct tpm_chip *chip = file->private_data;
+	struct tpm_file *fl = file->private_data;
 	size_t in_size = size, 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)
+	while (atomic_read(&fl->data_pending) != 0)
 		msleep(TPM_TIMEOUT);
 
-	mutex_lock(&chip->buffer_mutex);
+	mutex_lock(&fl->buffer_mutex);
 
-	if (in_size > TPM_BUFSIZE)
-		in_size = TPM_BUFSIZE;
+	in_size = min(sizeof(fl->data_bufferx), in_size);
 
 	if (copy_from_user
-	    (chip->data_buffer, (void __user *) buf, in_size)) {
-		mutex_unlock(&chip->buffer_mutex);
+	    (fl->data_bufferx, (void __user *) buf, in_size)) {
+		mutex_unlock(&fl->buffer_mutex);
 		return -EFAULT;
 	}
 
 	/* atomic tpm command send and result receive */
-	out_size = tpm_transmit(chip, chip->data_buffer, TPM_BUFSIZE);
+	out_size = tpm_transmit(fl->chip, fl->data_bufferx,
+				sizeof(fl->data_bufferx));
 
-	atomic_set(&chip->data_pending, out_size);
-	mutex_unlock(&chip->buffer_mutex);
+	atomic_set(&fl->data_pending, out_size);
+	mutex_unlock(&fl->buffer_mutex);
 
 	/* Set a timeout by which the reader must come claim the result */
-	mod_timer(&chip->user_read_timer, jiffies + (60 * HZ));
+	mod_timer(&fl->user_read_timer, jiffies + (60 * HZ));
 
 	return in_size;
 }
@@ -1251,26 +1245,25 @@ EXPORT_SYMBOL_GPL(tpm_write);
 ssize_t tpm_read(struct file *file, char __user *buf,
 		 size_t size, loff_t *off)
 {
-	struct tpm_chip *chip = file->private_data;
+	struct tpm_file *fl = file->private_data;
 	ssize_t ret_size;
 	int rc;
 
-	del_singleshot_timer_sync(&chip->user_read_timer);
-	flush_work_sync(&chip->work);
-	ret_size = atomic_read(&chip->data_pending);
-	atomic_set(&chip->data_pending, 0);
+	del_singleshot_timer_sync(&fl->user_read_timer);
+	flush_work_sync(&fl->work);
+	ret_size = atomic_read(&fl->data_pending);
+	atomic_set(&fl->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, orig_ret_size);
+		mutex_lock(&fl->buffer_mutex);
+		rc = copy_to_user(buf, fl->data_bufferx, ret_size);
+		memset(fl->data_bufferx, 0, orig_ret_size);
 		if (rc)
 			ret_size = -EFAULT;
-
-		mutex_unlock(&chip->buffer_mutex);
+		mutex_unlock(&fl->buffer_mutex);
 	}
 
 	return ret_size;
@@ -1413,15 +1406,9 @@ struct tpm_chip *tpm_register_hardware(struct device *dev,
 	if (chip == NULL || devname == NULL)
 		goto out_free;
 
-	mutex_init(&chip->buffer_mutex);
 	mutex_init(&chip->tpm_mutex);
 	INIT_LIST_HEAD(&chip->list);
 
-	INIT_WORK(&chip->work, timeout_work);
-
-	setup_timer(&chip->user_read_timer, user_reader_timeout,
-			(unsigned long)chip);
-
 	memcpy(&chip->vendor, entry, sizeof(struct tpm_vendor_specific));
 
 	chip->dev_num = find_first_zero_bit(dev_mask, TPM_NUM_DEVICES);
drivers/char/tpm/tpm.h +13 -10
index 2d583ef..58bde7c 100644
@@ -106,16 +106,6 @@ struct tpm_chip {
 	struct device *dev;	/* Device stuff */
 
 	int dev_num;		/* /dev/tpm# */
-	unsigned long is_open;	/* only one allowed */
-	int time_expired;
-
-	/* Data passed to and from the tpm via the read/write calls */
-	u8 *data_buffer;
-	atomic_t data_pending;
-	struct mutex buffer_mutex;
-
-	struct timer_list user_read_timer;	/* user needs to claim result */
-	struct work_struct work;
 	struct mutex tpm_mutex;	/* tpm is processing */
 
 	struct tpm_vendor_specific vendor;
@@ -132,6 +122,19 @@ static inline void tpm_chip_put(struct tpm_chip *chip)
 {
 	module_put(chip->dev->driver->owner);
 }
+/* Private data structure for struct file */
+struct tpm_file {
+	struct tpm_chip *chip;
+
+	/* Data passed to and from the tpm via the read/write calls */
+	atomic_t data_pending;
+	struct mutex buffer_mutex;
+
+	struct timer_list user_read_timer;	/* user needs to claim result */
+	struct work_struct work;
+
+	u8 data_bufferx[2048];
+};
 
 static inline int tpm_read_index(int base, int index)
 {
Signature · 1 line
1.7.4.1
#1 lore raw

Hi Jason,

one quick question:
- TPM_BUFSIZE = 4096,
[...]
+ u8 data_bufferx[2048];

Why do you half the buffer size?

Thanks,
Peter

#2 lore raw
Quoted 8 lines · On Mon, Oct 01, 2012 at 09:07:11AM +0000, [email protected] wrote:

Hi Jason,

one quick question:

(quoted diff) +1 -1
-	TPM_BUFSIZE = 4096,
[...]
+	u8 data_bufferx[2048];

Why do you half the buffer size?

I missed 7f366784f5c2b8fc065 when I rebased the patch, thanks!

Jason

#3 lore raw
In reply to Jason Gunthorpe
Quoted 3 lines · Jason Gunthorpe

How to use the TPM is really a user space policy choice, if the environment wants to use middleware then fine, but it is possible to make correct TPM apps without using middleware.

I'm not sure how I feel about this. The single open rule doesn't
prevent replacement of the middleware, it just requires a open()/close() around any use of the device node. That seems simple enough to me. In places where you do want TSS to be the sole opener, it can't enforce that rule itself, so I think we need to preserve the option of a single open at a minimum.

Kent

Quoted 16 lines

So, remove the kernel restriction that only one process may open the TPM.
- TPM low level functions (in kernel users) are already locked proprely and can run in parallel with the user space interface anyhow.
- Move the user space data buffer and related goop into a struct tpm_file, create one struct tpm_file per open file.

---
 drivers/char/tpm/tpm.c |   97 +++++++++++++++++++++---------------------------
 drivers/char/tpm/tpm.h |   23 ++++++-----
 2 files changed, 55 insertions(+), 65 deletions(-)

This is rebase, retest, resend of a patch I sent two years ago. The discussion on that earlier patch fizzled out. Resending incase there is renewed interest :)

#4 lore raw
Quoted 8 lines · Kent Yoder
Quoted 3 lines · Jason Gunthorpe

How to use the TPM is really a user space policy choice, if the environment wants to use middleware then fine, but it is possible to make correct TPM apps without using middleware.

I'm not sure how I feel about this. The single open rule doesn't
prevent replacement of the middleware, it just requires a open()/close()

I'm not interested in replacing the middleware, our designs do not use any middleware and several processes are required to access the TPM at once.

Using open/close is an interesting idea, but it wouldn't work. open() is coded to return EBUSY if another process has it open, rather than block, and spinning on open would be unacceptable.

Quoted 4 lines

around any use of the device node. That seems simple enough to me. In places where you do want TSS to be the sole opener, it can't enforce that rule itself, so I think we need to preserve the option of a single open at a minimum.

I agree, but I'm not sure how to expose this function? I've seen other places abuse O_EXCL for this, but that would not be compatible with existing implementations.

Three things come to mind
- O_EXCL means the open only succeeds if it the only open and prevents others (not compatible)
- Some other O_ flag is hijacked to mean the opposite of the above (yuk)
- A sysfs flag is added to turn on the new O_EXCL behavior

What do you think?

Jason

#5 lore raw

-----Original Message-----

Quoted 5 lines

From: Jason Gunthorpe [mailto:[email protected]]

Using open/close is an interesting idea, but it wouldn't work. open() is coded to return EBUSY if another process has it open, rather than block, and spinning on open would be unacceptable.

Hmm, maybe write a small pass through program which opens /dev/tpm once and accepts its data via a socket or pipe?

Thanks,
Peter

#6 lore raw
Quoted 8 lines · On Mon, Oct 15, 2012 at 08:35:09AM +0000, [email protected] wrote:
Quoted 5 lines

From: Jason Gunthorpe [mailto:[email protected]]

Using open/close is an interesting idea, but it wouldn't work. open() is coded to return EBUSY if another process has it open, rather than block, and spinning on open would be unacceptable.

Hmm, maybe write a small pass through program which opens /dev/tpm once and accepts its data via a socket or pipe?

I believe the kernel should not be enforcing this kind of policy into userspace. Plus, some of our embedded system are memory constrained so an unnecessary process is not welcome..

Jason

#7 lore raw
Quoted 7 lines · On Mon, 15 Oct 2012 10:39:45 -0600 Jason Gunthorpe…
Quoted 6 lines · On Mon, Oct 15, 2012 at 08:35:09AM +0000, [email protected] wrote:
Quoted 5 lines

From: Jason Gunthorpe [mailto:[email protected]]

Using open/close is an interesting idea, but it wouldn't work. open() is coded to return EBUSY if another process has it open, rather than block, and spinning on open would be unacceptable.

Quoted 6 lines
Quoted 2 lines

Hmm, maybe write a small pass through program which opens /dev/tpm once and accepts its data via a socket or pipe?

I believe the kernel should not be enforcing this kind of policy into userspace. Plus, some of our embedded system are memory constrained so an unnecessary process is not welcome..

Sane device drivers for devices where contention is meaningful block on an open that is busy, or return an error if the O_NONBLOCK option is specified. That's the normal case.

Alan

#8 lore raw
Quoted 7 lines · Alan Cox
Quoted 7 lines
Quoted 6 lines
Quoted 3 lines

Using open/close is an interesting idea, but it wouldn't work. open() is coded to return EBUSY if another process has it open, rather than block, and spinning on open would be unacceptable.

Hmm, maybe write a small pass through program which opens /dev/tpm once and accepts its data via a socket or pipe?

Quoted 7 lines
Quoted 3 lines

I believe the kernel should not be enforcing this kind of policy into userspace. Plus, some of our embedded system are memory constrained so an unnecessary process is not welcome..

Sane device drivers for devices where contention is meaningful block on an open that is busy, or return an error if the O_NONBLOCK option is specified. That's the normal case.

We have here a situation where there is no kernel or hardware requirement for exclusivity, but the current driver enforces it, for userspace only. Today the kernel and user space can access the TPM device concurrently.

So, I would like to migrate the userspace interface to allow non-exclusivity, but how do we do this?

Jason

#9 lore raw
In reply to Jason Gunthorpe
Quoted 3 lines

Using open/close is an interesting idea, but it wouldn't work. open() is coded to return EBUSY if another process has it open, rather than block, and spinning on open would be unacceptable.

I'm trying to imagine when spinning would be unacceptable? Keygen
could take several seconds, regardless of whether you can get the command to the chip right away or not...

Quoted 9 lines
Quoted 4 lines

around any use of the device node. That seems simple enough to me. In places where you do want TSS to be the sole opener, it can't enforce that rule itself, so I think we need to preserve the option of a single open at a minimum.

I agree, but I'm not sure how to expose this function? I've seen other places abuse O_EXCL for this, but that would not be compatible with existing implementations.

Quoted 8 lines

Three things come to mind
- O_EXCL means the open only succeeds if it the only open and prevents others (not compatible)
- Some other O_ flag is hijacked to mean the opposite of the above (yuk)
- A sysfs flag is added to turn on the new O_EXCL behavior

What do you think?

My first thought was a sysfs var to turn off exclusivity that
defaulted to today's behavior.

Kent

Quoted 2 lines

Jason