diff options
Diffstat (limited to 'security/tf_driver/tf_comm_tz.c')
-rw-r--r-- | security/tf_driver/tf_comm_tz.c | 885 |
1 files changed, 885 insertions, 0 deletions
diff --git a/security/tf_driver/tf_comm_tz.c b/security/tf_driver/tf_comm_tz.c new file mode 100644 index 000000000000..4c89de84accf --- /dev/null +++ b/security/tf_driver/tf_comm_tz.c @@ -0,0 +1,885 @@ +/** + * Copyright (c) 2011 Trusted Logic S.A. + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include <asm/div64.h> +#include <asm/system.h> +#include <linux/version.h> +#include <asm/cputype.h> +#include <linux/interrupt.h> +#include <linux/page-flags.h> +#include <linux/pagemap.h> +#include <linux/vmalloc.h> +#include <linux/jiffies.h> + +#include "tf_defs.h" +#include "tf_comm.h" +#include "tf_protocol.h" +#include "tf_util.h" +#include "tf_conn.h" + +/* + * Structure common to all SMC operations + */ +struct tf_generic_smc { + u32 reg0; + u32 reg1; + u32 reg2; + u32 reg3; + u32 reg4; +}; + +/*---------------------------------------------------------------------------- + * SMC operations + *----------------------------------------------------------------------------*/ + +static inline void tf_smc_generic_call( + struct tf_generic_smc *generic_smc) +{ +#ifdef CONFIG_SMP + long ret; + cpumask_t saved_cpu_mask; + cpumask_t local_cpu_mask = CPU_MASK_NONE; + + cpu_set(0, local_cpu_mask); + sched_getaffinity(0, &saved_cpu_mask); + ret = sched_setaffinity(0, &local_cpu_mask); + if (ret != 0) + dprintk(KERN_ERR "sched_setaffinity #1 -> 0x%lX", ret); +#endif + + __asm__ volatile( + "mov r0, %2\n" + "mov r1, %3\n" + "mov r2, %4\n" + "mov r3, %5\n" + "mov r4, %6\n" + ".word 0xe1600070 @ SMC 0\n" + "mov %0, r0\n" + "mov %1, r1\n" + : "=r" (generic_smc->reg0), "=r" (generic_smc->reg1) + : "r" (generic_smc->reg0), "r" (generic_smc->reg1), + "r" (generic_smc->reg2), "r" (generic_smc->reg3), + "r" (generic_smc->reg4) + : "r0", "r1", "r2", "r3", "r4"); + +#ifdef CONFIG_SMP + ret = sched_setaffinity(0, &saved_cpu_mask); + if (ret != 0) + dprintk(KERN_ERR "sched_setaffinity #2 -> 0x%lX", ret); +#endif +} + +/* + * Calls the get protocol version SMC. + * Fills the parameter pProtocolVersion with the version number returned by the + * SMC + */ +static inline void tf_smc_get_protocol_version(u32 *protocol_version) +{ + struct tf_generic_smc generic_smc; + + generic_smc.reg0 = TF_SMC_GET_PROTOCOL_VERSION; + generic_smc.reg1 = 0; + generic_smc.reg2 = 0; + generic_smc.reg3 = 0; + generic_smc.reg4 = 0; + + tf_smc_generic_call(&generic_smc); + *protocol_version = generic_smc.reg1; +} + + +/* + * Calls the init SMC with the specified parameters. + * Returns zero upon successful completion, or an appropriate error code upon + * failure. + */ +static inline int tf_smc_init(u32 shared_page_descriptor) +{ + struct tf_generic_smc generic_smc; + + generic_smc.reg0 = TF_SMC_INIT; + /* Descriptor for the layer 1 shared buffer */ + generic_smc.reg1 = shared_page_descriptor; + generic_smc.reg2 = 0; + generic_smc.reg3 = 0; + generic_smc.reg4 = 0; + + tf_smc_generic_call(&generic_smc); + if (generic_smc.reg0 != S_SUCCESS) + printk(KERN_ERR "tf_smc_init:" + " r0=0x%08X upon return (expected 0x%08X)!\n", + generic_smc.reg0, + S_SUCCESS); + + return generic_smc.reg0; +} + + +/* + * Calls the reset irq SMC. + */ +static inline void tf_smc_reset_irq(void) +{ + struct tf_generic_smc generic_smc; + + generic_smc.reg0 = TF_SMC_RESET_IRQ; + generic_smc.reg1 = 0; + generic_smc.reg2 = 0; + generic_smc.reg3 = 0; + generic_smc.reg4 = 0; + + tf_smc_generic_call(&generic_smc); +} + + +/* + * Calls the WAKE_UP SMC. + * Returns zero upon successful completion, or an appropriate error code upon + * failure. + */ +static inline int tf_smc_wake_up(u32 l1_shared_buffer_descriptor, + u32 shared_mem_start_offset, + u32 shared_mem_size) +{ + struct tf_generic_smc generic_smc; + + generic_smc.reg0 = TF_SMC_WAKE_UP; + generic_smc.reg1 = shared_mem_start_offset; + /* long form command */ + generic_smc.reg2 = shared_mem_size | 0x80000000; + generic_smc.reg3 = l1_shared_buffer_descriptor; + generic_smc.reg4 = 0; + + tf_smc_generic_call(&generic_smc); + + if (generic_smc.reg0 != S_SUCCESS) + printk(KERN_ERR "tf_smc_wake_up:" + " r0=0x%08X upon return (expected 0x%08X)!\n", + generic_smc.reg0, + S_SUCCESS); + + return generic_smc.reg0; +} + +/* + * Calls the N-Yield SMC. + */ +static inline void tf_smc_nyield(void) +{ + struct tf_generic_smc generic_smc; + + generic_smc.reg0 = TF_SMC_N_YIELD; + generic_smc.reg1 = 0; + generic_smc.reg2 = 0; + generic_smc.reg3 = 0; + generic_smc.reg4 = 0; + + tf_smc_generic_call(&generic_smc); +} + +/* Yields the Secure World */ +int tf_schedule_secure_world(struct tf_comm *comm) +{ + tf_set_current_time(comm); + + /* yield to the Secure World */ + tf_smc_nyield(); + + return 0; +} + +/* + * Returns the L2 descriptor for the specified user page. + */ + +#define L2_INIT_DESCRIPTOR_BASE (0x00000003) +#define L2_INIT_DESCRIPTOR_V13_12_SHIFT (4) + +static u32 tf_get_l2init_descriptor(u32 vaddr) +{ + struct page *page; + u32 paddr; + u32 descriptor; + + descriptor = L2_INIT_DESCRIPTOR_BASE; + + /* get physical address and add to descriptor */ + page = virt_to_page(vaddr); + paddr = page_to_phys(page); + descriptor |= (paddr & L2_DESCRIPTOR_ADDR_MASK); + + /* Add virtual address v[13:12] bits to descriptor */ + descriptor |= (DESCRIPTOR_V13_12_GET(vaddr) + << L2_INIT_DESCRIPTOR_V13_12_SHIFT); + + descriptor |= tf_get_l2_descriptor_common(vaddr, &init_mm); + + + return descriptor; +} + + +/*---------------------------------------------------------------------------- + * Power management + *----------------------------------------------------------------------------*/ + +/* + * Free the memory used by the W3B buffer for the specified comm. + * This function does nothing if no W3B buffer is allocated for the device. + */ +static inline void tf_free_w3b(struct tf_comm *comm) +{ + tf_cleanup_shared_memory( + &(comm->w3b_cpt_alloc_context), + &(comm->w3b_shmem_desc), + 0); + + tf_release_coarse_page_table_allocator(&(comm->w3b_cpt_alloc_context)); + + internal_vfree((void *)comm->w3b); + comm->w3b = 0; + comm->w3b_shmem_size = 0; + clear_bit(TF_COMM_FLAG_W3B_ALLOCATED, &(comm->flags)); +} + + +/* + * Allocates the W3B buffer for the specified comm. + * Returns zero upon successful completion, or an appropriate error code upon + * failure. + */ +static inline int tf_allocate_w3b(struct tf_comm *comm) +{ + int error; + u32 flags; + u32 config_flag_s; + u32 *w3b_descriptors; + u32 w3b_descriptor_count; + u32 w3b_current_size; + + config_flag_s = tf_read_reg32(&comm->l1_buffer->config_flag_s); + +retry: + if ((test_bit(TF_COMM_FLAG_W3B_ALLOCATED, &(comm->flags))) == 0) { + /* + * Initialize the shared memory for the W3B + */ + tf_init_coarse_page_table_allocator( + &comm->w3b_cpt_alloc_context); + } else { + /* + * The W3B is allocated but do we have to reallocate a bigger + * one? + */ + /* Check H bit */ + if ((config_flag_s & (1<<4)) != 0) { + /* The size of the W3B may change after SMC_INIT */ + /* Read the current value */ + w3b_current_size = tf_read_reg32( + &comm->l1_buffer->w3b_size_current_s); + if (comm->w3b_shmem_size > w3b_current_size) + return 0; + + tf_free_w3b(comm); + goto retry; + } else { + return 0; + } + } + + /* check H bit */ + if ((config_flag_s & (1<<4)) != 0) + /* The size of the W3B may change after SMC_INIT */ + /* Read the current value */ + comm->w3b_shmem_size = tf_read_reg32( + &comm->l1_buffer->w3b_size_current_s); + else + comm->w3b_shmem_size = tf_read_reg32( + &comm->l1_buffer->w3b_size_max_s); + + comm->w3b = (u32) internal_vmalloc(comm->w3b_shmem_size); + if (comm->w3b == 0) { + printk(KERN_ERR "tf_allocate_w3b():" + " Out of memory for W3B buffer (%u bytes)!\n", + (unsigned int)(comm->w3b_shmem_size)); + error = -ENOMEM; + goto error; + } + + /* initialize the w3b_shmem_desc structure */ + comm->w3b_shmem_desc.type = TF_SHMEM_TYPE_PM_HIBERNATE; + INIT_LIST_HEAD(&(comm->w3b_shmem_desc.list)); + + flags = (TF_SHMEM_TYPE_READ | TF_SHMEM_TYPE_WRITE); + + /* directly point to the L1 shared buffer W3B descriptors */ + w3b_descriptors = comm->l1_buffer->w3b_descriptors; + + /* + * tf_fill_descriptor_table uses the following parameter as an + * IN/OUT + */ + + error = tf_fill_descriptor_table( + &(comm->w3b_cpt_alloc_context), + &(comm->w3b_shmem_desc), + comm->w3b, + NULL, + w3b_descriptors, + comm->w3b_shmem_size, + &(comm->w3b_shmem_offset), + false, + flags, + &w3b_descriptor_count); + if (error != 0) { + printk(KERN_ERR "tf_allocate_w3b():" + " tf_fill_descriptor_table failed with " + "error code 0x%08x!\n", + error); + goto error; + } + + set_bit(TF_COMM_FLAG_W3B_ALLOCATED, &(comm->flags)); + + /* successful completion */ + return 0; + +error: + tf_free_w3b(comm); + + return error; +} + +/* + * Perform a Secure World shutdown operation. + * The routine does not return if the operation succeeds. + * the routine returns an appropriate error code if + * the operation fails. + */ +int tf_pm_shutdown(struct tf_comm *comm) +{ +#ifdef CONFIG_TFN + /* this function is useless for the TEGRA product */ + return 0; +#else + int error; + union tf_command command; + union tf_answer answer; + + dprintk(KERN_INFO "tf_pm_shutdown()\n"); + + memset(&command, 0, sizeof(command)); + + command.header.message_type = TF_MESSAGE_TYPE_MANAGEMENT; + command.header.message_size = + (sizeof(struct tf_command_management) - + sizeof(struct tf_command_header))/sizeof(u32); + + command.management.command = TF_MANAGEMENT_SHUTDOWN; + + error = tf_send_receive( + comm, + &command, + &answer, + NULL, + false); + + if (error != 0) { + dprintk(KERN_ERR "tf_pm_shutdown(): " + "tf_send_receive failed (error %d)!\n", + error); + return error; + } + +#ifdef CONFIG_TF_DRIVER_DEBUG_SUPPORT + if (answer.header.error_code != 0) + dprintk(KERN_ERR "tf_driver: shutdown failed.\n"); + else + dprintk(KERN_INFO "tf_driver: shutdown succeeded.\n"); +#endif + + return answer.header.error_code; +#endif +} + + +/* + * Perform a Secure World hibernate operation. + * The routine does not return if the operation succeeds. + * the routine returns an appropriate error code if + * the operation fails. + */ +int tf_pm_hibernate(struct tf_comm *comm) +{ +#ifdef CONFIG_TFN + /* this function is useless for the TEGRA product */ + return 0; +#else + int error; + union tf_command command; + union tf_answer answer; + u32 first_command; + u32 first_free_command; + + dprintk(KERN_INFO "tf_pm_hibernate()\n"); + + error = tf_allocate_w3b(comm); + if (error != 0) { + dprintk(KERN_ERR "tf_pm_hibernate(): " + "tf_allocate_w3b failed (error %d)!\n", + error); + return error; + } + + /* + * As the polling thread is already hibernating, we + * should send the message and receive the answer ourself + */ + + /* build the "prepare to hibernate" message */ + command.header.message_type = TF_MESSAGE_TYPE_MANAGEMENT; + command.management.command = TF_MANAGEMENT_HIBERNATE; + /* Long Form Command */ + command.management.shared_mem_descriptors[0] = 0; + command.management.shared_mem_descriptors[1] = 0; + command.management.w3b_size = + comm->w3b_shmem_size | 0x80000000; + command.management.w3b_start_offset = + comm->w3b_shmem_offset; + command.header.operation_id = (u32) &answer; + + tf_dump_command(&command); + + /* find a slot to send the message in */ + + /* AFY: why not use the function tf_send_receive?? We are + * duplicating a lot of subtle code here. And it's not going to be + * tested because power management is currently not supported by the + * secure world. */ + for (;;) { + int queue_words_count, command_size; + + spin_lock(&(comm->lock)); + + first_command = tf_read_reg32( + &comm->l1_buffer->first_command); + first_free_command = tf_read_reg32( + &comm->l1_buffer->first_free_command); + + queue_words_count = first_free_command - first_command; + command_size = command.header.message_size + + sizeof(struct tf_command_header); + if ((queue_words_count + command_size) < + TF_N_MESSAGE_QUEUE_CAPACITY) { + /* Command queue is not full */ + memcpy(&comm->l1_buffer->command_queue[ + first_free_command % + TF_N_MESSAGE_QUEUE_CAPACITY], + &command, + command_size * sizeof(u32)); + + tf_write_reg32(&comm->l1_buffer->first_free_command, + first_free_command + command_size); + + spin_unlock(&(comm->lock)); + break; + } + + spin_unlock(&(comm->lock)); + (void)tf_schedule_secure_world(comm); + } + + /* now wait for the answer, dispatching other answers */ + while (1) { + u32 first_answer; + u32 first_free_answer; + + /* check all the answers */ + first_free_answer = tf_read_reg32( + &comm->l1_buffer->first_free_answer); + first_answer = tf_read_reg32( + &comm->l1_buffer->first_answer); + + if (first_answer != first_free_answer) { + int bFoundAnswer = 0; + + do { + /* answer queue not empty */ + union tf_answer tmp_answer; + struct tf_answer_header header; + /* size of the command in words of 32bit */ + int command_size; + + /* get the message_size */ + memcpy(&header, + &comm->l1_buffer->answer_queue[ + first_answer % + TF_S_ANSWER_QUEUE_CAPACITY], + sizeof(struct tf_answer_header)); + command_size = header.message_size + + sizeof(struct tf_answer_header); + + /* + * NOTE: message_size is the number of words + * following the first word + */ + memcpy(&tmp_answer, + &comm->l1_buffer->answer_queue[ + first_answer % + TF_S_ANSWER_QUEUE_CAPACITY], + command_size * sizeof(u32)); + + tf_dump_answer(&tmp_answer); + + if (tmp_answer.header.operation_id == + (u32) &answer) { + /* + * this is the answer to the "prepare to + * hibernate" message + */ + memcpy(&answer, + &tmp_answer, + command_size * sizeof(u32)); + + bFoundAnswer = 1; + tf_write_reg32( + &comm->l1_buffer->first_answer, + first_answer + command_size); + break; + } else { + /* + * this is a standard message answer, + * dispatch it + */ + struct tf_answer_struct + *answerStructure; + + answerStructure = + (struct tf_answer_struct *) + tmp_answer.header.operation_id; + + memcpy(answerStructure->answer, + &tmp_answer, + command_size * sizeof(u32)); + + answerStructure->answer_copied = true; + } + + tf_write_reg32( + &comm->l1_buffer->first_answer, + first_answer + command_size); + } while (first_answer != first_free_answer); + + if (bFoundAnswer) + break; + } + + /* + * since the Secure World is at least running the "prepare to + * hibernate" message, its timeout must be immediate So there is + * no need to check its timeout and schedule() the current + * thread + */ + (void)tf_schedule_secure_world(comm); + } /* while (1) */ + + printk(KERN_INFO "tf_driver: hibernate.\n"); + return 0; +#endif +} + + +/* + * Perform a Secure World resume operation. + * The routine returns once the Secure World is active again + * or if an error occurs during the "resume" process + */ +int tf_pm_resume(struct tf_comm *comm) +{ +#ifdef CONFIG_TFN + /* this function is useless for the TEGRA product */ + return 0; +#else + int error; + u32 status; + + dprintk(KERN_INFO "tf_pm_resume()\n"); + + error = tf_smc_wake_up( + tf_get_l2init_descriptor((u32)comm->l1_buffer), + comm->w3b_shmem_offset, + comm->w3b_shmem_size); + + if (error != 0) { + dprintk(KERN_ERR "tf_pm_resume(): " + "tf_smc_wake_up failed (error %d)!\n", + error); + return error; + } + + status = ((tf_read_reg32(&(comm->l1_buffer->status_s)) + & TF_STATUS_POWER_STATE_MASK) + >> TF_STATUS_POWER_STATE_SHIFT); + + while ((status != TF_POWER_MODE_ACTIVE) + && (status != TF_POWER_MODE_PANIC)) { + tf_smc_nyield(); + + status = ((tf_read_reg32(&(comm->l1_buffer->status_s)) + & TF_STATUS_POWER_STATE_MASK) + >> TF_STATUS_POWER_STATE_SHIFT); + + /* + * As this may last quite a while, call the kernel scheduler to + * hand over CPU for other operations + */ + schedule(); + } + + switch (status) { + case TF_POWER_MODE_ACTIVE: + break; + + case TF_POWER_MODE_PANIC: + dprintk(KERN_ERR "tf_pm_resume(): " + "Secure World POWER_MODE_PANIC!\n"); + return -EINVAL; + + default: + dprintk(KERN_ERR "tf_pm_resume(): " + "unexpected Secure World POWER_MODE (%d)!\n", status); + return -EINVAL; + } + + dprintk(KERN_INFO "tf_pm_resume() succeeded\n"); + return 0; +#endif +} + +/*---------------------------------------------------------------------------- + * Communication initialization and termination + *----------------------------------------------------------------------------*/ + +/* + * Handles the software interrupts issued by the Secure World. + */ +static irqreturn_t tf_soft_int_handler(int irq, void *dev_id) +{ + struct tf_comm *comm = (struct tf_comm *) dev_id; + + if (comm->l1_buffer == NULL) + return IRQ_NONE; + + if ((tf_read_reg32(&comm->l1_buffer->status_s) & + TF_STATUS_P_MASK) == 0) + /* interrupt not issued by the Trusted Foundations Software */ + return IRQ_NONE; + + tf_smc_reset_irq(); + + /* signal N_SM_EVENT */ + wake_up(&comm->wait_queue); + + return IRQ_HANDLED; +} + +/* + * Initializes the communication with the Secure World. + * The L1 shared buffer is allocated and the Secure World + * is yielded for the first time. + * returns successfuly once the communication with + * the Secure World is up and running + * + * Returns 0 upon success or appropriate error code + * upon failure + */ +int tf_init(struct tf_comm *comm) +{ + int error; + struct page *buffer_page; + u32 protocol_version; + + dprintk(KERN_INFO "tf_init()\n"); + + spin_lock_init(&(comm->lock)); + comm->flags = 0; + comm->l1_buffer = NULL; + init_waitqueue_head(&(comm->wait_queue)); + + /* + * Check the Secure World protocol version is the expected one. + */ + tf_smc_get_protocol_version(&protocol_version); + + if ((GET_PROTOCOL_MAJOR_VERSION(protocol_version)) + != TF_S_PROTOCOL_MAJOR_VERSION) { + printk(KERN_ERR "tf_init():" + " Unsupported Secure World Major Version " + "(0x%02X, expected 0x%02X)!\n", + GET_PROTOCOL_MAJOR_VERSION(protocol_version), + TF_S_PROTOCOL_MAJOR_VERSION); + error = -EIO; + goto error; + } + + /* + * Register the software interrupt handler if required to. + */ + if (comm->soft_int_irq != -1) { + dprintk(KERN_INFO "tf_init(): " + "Registering software interrupt handler (IRQ %d)\n", + comm->soft_int_irq); + + error = request_irq(comm->soft_int_irq, + tf_soft_int_handler, + IRQF_SHARED, + TF_DEVICE_BASE_NAME, + comm); + if (error != 0) { + dprintk(KERN_ERR "tf_init(): " + "request_irq failed for irq %d (error %d)\n", + comm->soft_int_irq, error); + goto error; + } + set_bit(TF_COMM_FLAG_IRQ_REQUESTED, &(comm->flags)); + } + + /* + * Allocate and initialize the L1 shared buffer. + */ + comm->l1_buffer = (void *) internal_get_zeroed_page(GFP_KERNEL); + if (comm->l1_buffer == NULL) { + printk(KERN_ERR "tf_init():" + " get_zeroed_page failed for L1 shared buffer!\n"); + error = -ENOMEM; + goto error; + } + + /* + * Ensure the page storing the L1 shared buffer is mapped. + */ + buffer_page = virt_to_page(comm->l1_buffer); + trylock_page(buffer_page); + + dprintk(KERN_INFO "tf_init(): " + "L1 shared buffer allocated at virtual:%p, " + "physical:%p (page:%p)\n", + comm->l1_buffer, + (void *)virt_to_phys(comm->l1_buffer), + buffer_page); + + set_bit(TF_COMM_FLAG_L1_SHARED_ALLOCATED, &(comm->flags)); + + /* + * Init SMC + */ + error = tf_smc_init( + tf_get_l2init_descriptor((u32)comm->l1_buffer)); + if (error != S_SUCCESS) { + dprintk(KERN_ERR "tf_init(): " + "tf_smc_init failed (error 0x%08X)!\n", + error); + goto error; + } + + /* + * check whether the interrupts are actually enabled + * If not, remove irq handler + */ + if ((tf_read_reg32(&comm->l1_buffer->config_flag_s) & + TF_CONFIG_FLAG_S) == 0) { + if (test_and_clear_bit(TF_COMM_FLAG_IRQ_REQUESTED, + &(comm->flags)) != 0) { + dprintk(KERN_INFO "tf_init(): " + "Interrupts not used, unregistering " + "softint (IRQ %d)\n", + comm->soft_int_irq); + + free_irq(comm->soft_int_irq, comm); + } + } else { + if (test_bit(TF_COMM_FLAG_IRQ_REQUESTED, + &(comm->flags)) == 0) { + /* + * Interrupts are enabled in the Secure World, but not + * handled by driver + */ + dprintk(KERN_ERR "tf_init(): " + "soft_interrupt argument not provided\n"); + error = -EINVAL; + goto error; + } + } + + /* + * Successful completion. + */ + + /* yield for the first time */ + (void)tf_schedule_secure_world(comm); + + dprintk(KERN_INFO "tf_init(): Success\n"); + return S_SUCCESS; + +error: + /* + * Error handling. + */ + dprintk(KERN_INFO "tf_init(): Failure (error %d)\n", + error); + tf_terminate(comm); + return error; +} + + +/* + * Attempt to terminate the communication with the Secure World. + * The L1 shared buffer is freed. + * Calling this routine terminates definitaly the communication + * with the Secure World : there is no way to inform the Secure World of a new + * L1 shared buffer to be used once it has been initialized. + */ +void tf_terminate(struct tf_comm *comm) +{ + dprintk(KERN_INFO "tf_terminate()\n"); + + set_bit(TF_COMM_FLAG_TERMINATING, &(comm->flags)); + + if ((test_bit(TF_COMM_FLAG_W3B_ALLOCATED, + &(comm->flags))) != 0) { + dprintk(KERN_INFO "tf_terminate(): " + "Freeing the W3B buffer...\n"); + tf_free_w3b(comm); + } + + if ((test_bit(TF_COMM_FLAG_L1_SHARED_ALLOCATED, + &(comm->flags))) != 0) { + __clear_page_locked(virt_to_page(comm->l1_buffer)); + internal_free_page((unsigned long) comm->l1_buffer); + } + + if ((test_bit(TF_COMM_FLAG_IRQ_REQUESTED, + &(comm->flags))) != 0) { + dprintk(KERN_INFO "tf_terminate(): " + "Unregistering softint (IRQ %d)\n", + comm->soft_int_irq); + free_irq(comm->soft_int_irq, comm); + } +} |