|  | /* | 
|  | * Copyright 2012 Tilera Corporation. 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 | 
|  | *   as published by the Free Software Foundation, version 2. | 
|  | * | 
|  | *   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, GOOD TITLE or | 
|  | *   NON INFRINGEMENT.  See the GNU General Public License for | 
|  | *   more details. | 
|  | */ | 
|  |  | 
|  | /* | 
|  | * Implementation of mpipe gxio calls. | 
|  | */ | 
|  |  | 
|  | #include <linux/errno.h> | 
|  | #include <linux/io.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/string.h> | 
|  |  | 
|  | #include <gxio/iorpc_globals.h> | 
|  | #include <gxio/iorpc_mpipe.h> | 
|  | #include <gxio/iorpc_mpipe_info.h> | 
|  | #include <gxio/kiorpc.h> | 
|  | #include <gxio/mpipe.h> | 
|  |  | 
|  | /* HACK: Avoid pointless "shadow" warnings. */ | 
|  | #define link link_shadow | 
|  |  | 
|  | int gxio_mpipe_init(gxio_mpipe_context_t *context, unsigned int mpipe_index) | 
|  | { | 
|  | char file[32]; | 
|  |  | 
|  | int fd; | 
|  | int i; | 
|  |  | 
|  | if (mpipe_index >= GXIO_MPIPE_INSTANCE_MAX) | 
|  | return -EINVAL; | 
|  |  | 
|  | snprintf(file, sizeof(file), "mpipe/%d/iorpc", mpipe_index); | 
|  | fd = hv_dev_open((HV_VirtAddr) file, 0); | 
|  |  | 
|  | context->fd = fd; | 
|  |  | 
|  | if (fd < 0) { | 
|  | if (fd >= GXIO_ERR_MIN && fd <= GXIO_ERR_MAX) | 
|  | return fd; | 
|  | else | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  | /* Map in the MMIO space. */ | 
|  | context->mmio_cfg_base = (void __force *) | 
|  | iorpc_ioremap(fd, HV_MPIPE_CONFIG_MMIO_OFFSET, | 
|  | HV_MPIPE_CONFIG_MMIO_SIZE); | 
|  | if (context->mmio_cfg_base == NULL) | 
|  | goto cfg_failed; | 
|  |  | 
|  | context->mmio_fast_base = (void __force *) | 
|  | iorpc_ioremap(fd, HV_MPIPE_FAST_MMIO_OFFSET, | 
|  | HV_MPIPE_FAST_MMIO_SIZE); | 
|  | if (context->mmio_fast_base == NULL) | 
|  | goto fast_failed; | 
|  |  | 
|  | /* Initialize the stacks. */ | 
|  | for (i = 0; i < 8; i++) | 
|  | context->__stacks.stacks[i] = 255; | 
|  |  | 
|  | context->instance = mpipe_index; | 
|  |  | 
|  | return 0; | 
|  |  | 
|  | fast_failed: | 
|  | iounmap((void __force __iomem *)(context->mmio_cfg_base)); | 
|  | cfg_failed: | 
|  | hv_dev_close(context->fd); | 
|  | context->fd = -1; | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  | EXPORT_SYMBOL_GPL(gxio_mpipe_init); | 
|  |  | 
|  | int gxio_mpipe_destroy(gxio_mpipe_context_t *context) | 
|  | { | 
|  | iounmap((void __force __iomem *)(context->mmio_cfg_base)); | 
|  | iounmap((void __force __iomem *)(context->mmio_fast_base)); | 
|  | return hv_dev_close(context->fd); | 
|  | } | 
|  |  | 
|  | EXPORT_SYMBOL_GPL(gxio_mpipe_destroy); | 
|  |  | 
|  | static int16_t gxio_mpipe_buffer_sizes[8] = | 
|  | { 128, 256, 512, 1024, 1664, 4096, 10368, 16384 }; | 
|  |  | 
|  | gxio_mpipe_buffer_size_enum_t gxio_mpipe_buffer_size_to_buffer_size_enum(size_t | 
|  | size) | 
|  | { | 
|  | int i; | 
|  | for (i = 0; i < 7; i++) | 
|  | if (size <= gxio_mpipe_buffer_sizes[i]) | 
|  | break; | 
|  | return i; | 
|  | } | 
|  |  | 
|  | EXPORT_SYMBOL_GPL(gxio_mpipe_buffer_size_to_buffer_size_enum); | 
|  |  | 
|  | size_t gxio_mpipe_buffer_size_enum_to_buffer_size(gxio_mpipe_buffer_size_enum_t | 
|  | buffer_size_enum) | 
|  | { | 
|  | if (buffer_size_enum > 7) | 
|  | buffer_size_enum = 7; | 
|  |  | 
|  | return gxio_mpipe_buffer_sizes[buffer_size_enum]; | 
|  | } | 
|  |  | 
|  | EXPORT_SYMBOL_GPL(gxio_mpipe_buffer_size_enum_to_buffer_size); | 
|  |  | 
|  | size_t gxio_mpipe_calc_buffer_stack_bytes(unsigned long buffers) | 
|  | { | 
|  | const int BUFFERS_PER_LINE = 12; | 
|  |  | 
|  | /* Count the number of cachelines. */ | 
|  | unsigned long lines = | 
|  | (buffers + BUFFERS_PER_LINE - 1) / BUFFERS_PER_LINE; | 
|  |  | 
|  | /* Convert to bytes. */ | 
|  | return lines * CHIP_L2_LINE_SIZE(); | 
|  | } | 
|  |  | 
|  | EXPORT_SYMBOL_GPL(gxio_mpipe_calc_buffer_stack_bytes); | 
|  |  | 
|  | int gxio_mpipe_init_buffer_stack(gxio_mpipe_context_t *context, | 
|  | unsigned int stack, | 
|  | gxio_mpipe_buffer_size_enum_t | 
|  | buffer_size_enum, void *mem, size_t mem_size, | 
|  | unsigned int mem_flags) | 
|  | { | 
|  | int result; | 
|  |  | 
|  | memset(mem, 0, mem_size); | 
|  |  | 
|  | result = gxio_mpipe_init_buffer_stack_aux(context, mem, mem_size, | 
|  | mem_flags, stack, | 
|  | buffer_size_enum); | 
|  | if (result < 0) | 
|  | return result; | 
|  |  | 
|  | /* Save the stack. */ | 
|  | context->__stacks.stacks[buffer_size_enum] = stack; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | EXPORT_SYMBOL_GPL(gxio_mpipe_init_buffer_stack); | 
|  |  | 
|  | int gxio_mpipe_init_notif_ring(gxio_mpipe_context_t *context, | 
|  | unsigned int ring, | 
|  | void *mem, size_t mem_size, | 
|  | unsigned int mem_flags) | 
|  | { | 
|  | return gxio_mpipe_init_notif_ring_aux(context, mem, mem_size, | 
|  | mem_flags, ring); | 
|  | } | 
|  |  | 
|  | EXPORT_SYMBOL_GPL(gxio_mpipe_init_notif_ring); | 
|  |  | 
|  | int gxio_mpipe_init_notif_group_and_buckets(gxio_mpipe_context_t *context, | 
|  | unsigned int group, | 
|  | unsigned int ring, | 
|  | unsigned int num_rings, | 
|  | unsigned int bucket, | 
|  | unsigned int num_buckets, | 
|  | gxio_mpipe_bucket_mode_t mode) | 
|  | { | 
|  | int i; | 
|  | int result; | 
|  |  | 
|  | gxio_mpipe_bucket_info_t bucket_info = { { | 
|  | .group = group, | 
|  | .mode = mode, | 
|  | } | 
|  | }; | 
|  |  | 
|  | gxio_mpipe_notif_group_bits_t bits = { {0} }; | 
|  |  | 
|  | for (i = 0; i < num_rings; i++) | 
|  | gxio_mpipe_notif_group_add_ring(&bits, ring + i); | 
|  |  | 
|  | result = gxio_mpipe_init_notif_group(context, group, bits); | 
|  | if (result != 0) | 
|  | return result; | 
|  |  | 
|  | for (i = 0; i < num_buckets; i++) { | 
|  | bucket_info.notifring = ring + (i % num_rings); | 
|  |  | 
|  | result = gxio_mpipe_init_bucket(context, bucket + i, | 
|  | bucket_info); | 
|  | if (result != 0) | 
|  | return result; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | EXPORT_SYMBOL_GPL(gxio_mpipe_init_notif_group_and_buckets); | 
|  |  | 
|  | int gxio_mpipe_init_edma_ring(gxio_mpipe_context_t *context, | 
|  | unsigned int ring, unsigned int channel, | 
|  | void *mem, size_t mem_size, | 
|  | unsigned int mem_flags) | 
|  | { | 
|  | memset(mem, 0, mem_size); | 
|  |  | 
|  | return gxio_mpipe_init_edma_ring_aux(context, mem, mem_size, mem_flags, | 
|  | ring, channel); | 
|  | } | 
|  |  | 
|  | EXPORT_SYMBOL_GPL(gxio_mpipe_init_edma_ring); | 
|  |  | 
|  | void gxio_mpipe_rules_init(gxio_mpipe_rules_t *rules, | 
|  | gxio_mpipe_context_t *context) | 
|  | { | 
|  | rules->context = context; | 
|  | memset(&rules->list, 0, sizeof(rules->list)); | 
|  | } | 
|  |  | 
|  | EXPORT_SYMBOL_GPL(gxio_mpipe_rules_init); | 
|  |  | 
|  | int gxio_mpipe_rules_begin(gxio_mpipe_rules_t *rules, | 
|  | unsigned int bucket, unsigned int num_buckets, | 
|  | gxio_mpipe_rules_stacks_t *stacks) | 
|  | { | 
|  | int i; | 
|  | int stack = 255; | 
|  |  | 
|  | gxio_mpipe_rules_list_t *list = &rules->list; | 
|  |  | 
|  | /* Current rule. */ | 
|  | gxio_mpipe_rules_rule_t *rule = | 
|  | (gxio_mpipe_rules_rule_t *) (list->rules + list->head); | 
|  |  | 
|  | unsigned int head = list->tail; | 
|  |  | 
|  | /* | 
|  | * Align next rule properly. | 
|  | *Note that "dmacs_and_vlans" will also be aligned. | 
|  | */ | 
|  | unsigned int pad = 0; | 
|  | while (((head + pad) % __alignof__(gxio_mpipe_rules_rule_t)) != 0) | 
|  | pad++; | 
|  |  | 
|  | /* | 
|  | * Verify room. | 
|  | * ISSUE: Mark rules as broken on error? | 
|  | */ | 
|  | if (head + pad + sizeof(*rule) >= sizeof(list->rules)) | 
|  | return GXIO_MPIPE_ERR_RULES_FULL; | 
|  |  | 
|  | /* Verify num_buckets is a power of 2. */ | 
|  | if (__builtin_popcount(num_buckets) != 1) | 
|  | return GXIO_MPIPE_ERR_RULES_INVALID; | 
|  |  | 
|  | /* Add padding to previous rule. */ | 
|  | rule->size += pad; | 
|  |  | 
|  | /* Start a new rule. */ | 
|  | list->head = head + pad; | 
|  |  | 
|  | rule = (gxio_mpipe_rules_rule_t *) (list->rules + list->head); | 
|  |  | 
|  | /* Default some values. */ | 
|  | rule->headroom = 2; | 
|  | rule->tailroom = 0; | 
|  | rule->capacity = 16384; | 
|  |  | 
|  | /* Save the bucket info. */ | 
|  | rule->bucket_mask = num_buckets - 1; | 
|  | rule->bucket_first = bucket; | 
|  |  | 
|  | for (i = 8 - 1; i >= 0; i--) { | 
|  | int maybe = | 
|  | stacks ? stacks->stacks[i] : rules->context->__stacks. | 
|  | stacks[i]; | 
|  | if (maybe != 255) | 
|  | stack = maybe; | 
|  | rule->stacks.stacks[i] = stack; | 
|  | } | 
|  |  | 
|  | if (stack == 255) | 
|  | return GXIO_MPIPE_ERR_RULES_INVALID; | 
|  |  | 
|  | /* NOTE: Only entries at the end of the array can be 255. */ | 
|  | for (i = 8 - 1; i > 0; i--) { | 
|  | if (rule->stacks.stacks[i] == 255) { | 
|  | rule->stacks.stacks[i] = stack; | 
|  | rule->capacity = | 
|  | gxio_mpipe_buffer_size_enum_to_buffer_size(i - | 
|  | 1); | 
|  | } | 
|  | } | 
|  |  | 
|  | rule->size = sizeof(*rule); | 
|  | list->tail = list->head + rule->size; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | EXPORT_SYMBOL_GPL(gxio_mpipe_rules_begin); | 
|  |  | 
|  | int gxio_mpipe_rules_add_channel(gxio_mpipe_rules_t *rules, | 
|  | unsigned int channel) | 
|  | { | 
|  | gxio_mpipe_rules_list_t *list = &rules->list; | 
|  |  | 
|  | gxio_mpipe_rules_rule_t *rule = | 
|  | (gxio_mpipe_rules_rule_t *) (list->rules + list->head); | 
|  |  | 
|  | /* Verify channel. */ | 
|  | if (channel >= 32) | 
|  | return GXIO_MPIPE_ERR_RULES_INVALID; | 
|  |  | 
|  | /* Verify begun. */ | 
|  | if (list->tail == 0) | 
|  | return GXIO_MPIPE_ERR_RULES_EMPTY; | 
|  |  | 
|  | rule->channel_bits |= (1UL << channel); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | EXPORT_SYMBOL_GPL(gxio_mpipe_rules_add_channel); | 
|  |  | 
|  | int gxio_mpipe_rules_set_headroom(gxio_mpipe_rules_t *rules, uint8_t headroom) | 
|  | { | 
|  | gxio_mpipe_rules_list_t *list = &rules->list; | 
|  |  | 
|  | gxio_mpipe_rules_rule_t *rule = | 
|  | (gxio_mpipe_rules_rule_t *) (list->rules + list->head); | 
|  |  | 
|  | /* Verify begun. */ | 
|  | if (list->tail == 0) | 
|  | return GXIO_MPIPE_ERR_RULES_EMPTY; | 
|  |  | 
|  | rule->headroom = headroom; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | EXPORT_SYMBOL_GPL(gxio_mpipe_rules_set_headroom); | 
|  |  | 
|  | int gxio_mpipe_rules_commit(gxio_mpipe_rules_t *rules) | 
|  | { | 
|  | gxio_mpipe_rules_list_t *list = &rules->list; | 
|  | unsigned int size = | 
|  | offsetof(gxio_mpipe_rules_list_t, rules) + list->tail; | 
|  | return gxio_mpipe_commit_rules(rules->context, list, size); | 
|  | } | 
|  |  | 
|  | EXPORT_SYMBOL_GPL(gxio_mpipe_rules_commit); | 
|  |  | 
|  | int gxio_mpipe_iqueue_init(gxio_mpipe_iqueue_t *iqueue, | 
|  | gxio_mpipe_context_t *context, | 
|  | unsigned int ring, | 
|  | void *mem, size_t mem_size, unsigned int mem_flags) | 
|  | { | 
|  | /* The init call below will verify that "mem_size" is legal. */ | 
|  | unsigned int num_entries = mem_size / sizeof(gxio_mpipe_idesc_t); | 
|  |  | 
|  | iqueue->context = context; | 
|  | iqueue->idescs = (gxio_mpipe_idesc_t *)mem; | 
|  | iqueue->ring = ring; | 
|  | iqueue->num_entries = num_entries; | 
|  | iqueue->mask_num_entries = num_entries - 1; | 
|  | iqueue->log2_num_entries = __builtin_ctz(num_entries); | 
|  | iqueue->head = 1; | 
|  | #ifdef __BIG_ENDIAN__ | 
|  | iqueue->swapped = 0; | 
|  | #endif | 
|  |  | 
|  | /* Initialize the "tail". */ | 
|  | __gxio_mmio_write(mem, iqueue->head); | 
|  |  | 
|  | return gxio_mpipe_init_notif_ring(context, ring, mem, mem_size, | 
|  | mem_flags); | 
|  | } | 
|  |  | 
|  | EXPORT_SYMBOL_GPL(gxio_mpipe_iqueue_init); | 
|  |  | 
|  | int gxio_mpipe_equeue_init(gxio_mpipe_equeue_t *equeue, | 
|  | gxio_mpipe_context_t *context, | 
|  | unsigned int ering, | 
|  | unsigned int channel, | 
|  | void *mem, unsigned int mem_size, | 
|  | unsigned int mem_flags) | 
|  | { | 
|  | /* The init call below will verify that "mem_size" is legal. */ | 
|  | unsigned int num_entries = mem_size / sizeof(gxio_mpipe_edesc_t); | 
|  |  | 
|  | /* Offset used to read number of completed commands. */ | 
|  | MPIPE_EDMA_POST_REGION_ADDR_t offset; | 
|  |  | 
|  | int result = gxio_mpipe_init_edma_ring(context, ering, channel, | 
|  | mem, mem_size, mem_flags); | 
|  | if (result < 0) | 
|  | return result; | 
|  |  | 
|  | memset(equeue, 0, sizeof(*equeue)); | 
|  |  | 
|  | offset.word = 0; | 
|  | offset.region = | 
|  | MPIPE_MMIO_ADDR__REGION_VAL_EDMA - | 
|  | MPIPE_MMIO_ADDR__REGION_VAL_IDMA; | 
|  | offset.ring = ering; | 
|  |  | 
|  | __gxio_dma_queue_init(&equeue->dma_queue, | 
|  | context->mmio_fast_base + offset.word, | 
|  | num_entries); | 
|  | equeue->edescs = mem; | 
|  | equeue->mask_num_entries = num_entries - 1; | 
|  | equeue->log2_num_entries = __builtin_ctz(num_entries); | 
|  | equeue->context = context; | 
|  | equeue->ering = ering; | 
|  | equeue->channel = channel; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | EXPORT_SYMBOL_GPL(gxio_mpipe_equeue_init); | 
|  |  | 
|  | int gxio_mpipe_set_timestamp(gxio_mpipe_context_t *context, | 
|  | const struct timespec64 *ts) | 
|  | { | 
|  | cycles_t cycles = get_cycles(); | 
|  | return gxio_mpipe_set_timestamp_aux(context, (uint64_t)ts->tv_sec, | 
|  | (uint64_t)ts->tv_nsec, | 
|  | (uint64_t)cycles); | 
|  | } | 
|  | EXPORT_SYMBOL_GPL(gxio_mpipe_set_timestamp); | 
|  |  | 
|  | int gxio_mpipe_get_timestamp(gxio_mpipe_context_t *context, | 
|  | struct timespec64 *ts) | 
|  | { | 
|  | int ret; | 
|  | cycles_t cycles_prev, cycles_now, clock_rate; | 
|  | cycles_prev = get_cycles(); | 
|  | ret = gxio_mpipe_get_timestamp_aux(context, (uint64_t *)&ts->tv_sec, | 
|  | (uint64_t *)&ts->tv_nsec, | 
|  | (uint64_t *)&cycles_now); | 
|  | if (ret < 0) { | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | clock_rate = get_clock_rate(); | 
|  | ts->tv_nsec -= (cycles_now - cycles_prev) * 1000000000LL / clock_rate; | 
|  | if (ts->tv_nsec < 0) { | 
|  | ts->tv_nsec += 1000000000LL; | 
|  | ts->tv_sec -= 1; | 
|  | } | 
|  | return ret; | 
|  | } | 
|  | EXPORT_SYMBOL_GPL(gxio_mpipe_get_timestamp); | 
|  |  | 
|  | int gxio_mpipe_adjust_timestamp(gxio_mpipe_context_t *context, int64_t delta) | 
|  | { | 
|  | return gxio_mpipe_adjust_timestamp_aux(context, delta); | 
|  | } | 
|  | EXPORT_SYMBOL_GPL(gxio_mpipe_adjust_timestamp); | 
|  |  | 
|  | /* Get our internal context used for link name access.  This context is | 
|  | *  special in that it is not associated with an mPIPE service domain. | 
|  | */ | 
|  | static gxio_mpipe_context_t *_gxio_get_link_context(void) | 
|  | { | 
|  | static gxio_mpipe_context_t context; | 
|  | static gxio_mpipe_context_t *contextp; | 
|  | static int tried_open = 0; | 
|  | static DEFINE_MUTEX(mutex); | 
|  |  | 
|  | mutex_lock(&mutex); | 
|  |  | 
|  | if (!tried_open) { | 
|  | int i = 0; | 
|  | tried_open = 1; | 
|  |  | 
|  | /* | 
|  | * "4" here is the maximum possible number of mPIPE shims; it's | 
|  | * an exaggeration but we shouldn't ever go beyond 2 anyway. | 
|  | */ | 
|  | for (i = 0; i < 4; i++) { | 
|  | char file[80]; | 
|  |  | 
|  | snprintf(file, sizeof(file), "mpipe/%d/iorpc_info", i); | 
|  | context.fd = hv_dev_open((HV_VirtAddr) file, 0); | 
|  | if (context.fd < 0) | 
|  | continue; | 
|  |  | 
|  | contextp = &context; | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | mutex_unlock(&mutex); | 
|  |  | 
|  | return contextp; | 
|  | } | 
|  |  | 
|  | int gxio_mpipe_link_instance(const char *link_name) | 
|  | { | 
|  | _gxio_mpipe_link_name_t name; | 
|  | gxio_mpipe_context_t *context = _gxio_get_link_context(); | 
|  |  | 
|  | if (!context) | 
|  | return GXIO_ERR_NO_DEVICE; | 
|  |  | 
|  | if (strscpy(name.name, link_name, sizeof(name.name)) < 0) | 
|  | return GXIO_ERR_NO_DEVICE; | 
|  |  | 
|  | return gxio_mpipe_info_instance_aux(context, name); | 
|  | } | 
|  | EXPORT_SYMBOL_GPL(gxio_mpipe_link_instance); | 
|  |  | 
|  | int gxio_mpipe_link_enumerate_mac(int idx, char *link_name, uint8_t *link_mac) | 
|  | { | 
|  | int rv; | 
|  | _gxio_mpipe_link_name_t name; | 
|  | _gxio_mpipe_link_mac_t mac; | 
|  |  | 
|  | gxio_mpipe_context_t *context = _gxio_get_link_context(); | 
|  | if (!context) | 
|  | return GXIO_ERR_NO_DEVICE; | 
|  |  | 
|  | rv = gxio_mpipe_info_enumerate_aux(context, idx, &name, &mac); | 
|  | if (rv >= 0) { | 
|  | if (strscpy(link_name, name.name, sizeof(name.name)) < 0) | 
|  | return GXIO_ERR_INVAL_MEMORY_SIZE; | 
|  | memcpy(link_mac, mac.mac, sizeof(mac.mac)); | 
|  | } | 
|  |  | 
|  | return rv; | 
|  | } | 
|  |  | 
|  | EXPORT_SYMBOL_GPL(gxio_mpipe_link_enumerate_mac); | 
|  |  | 
|  | int gxio_mpipe_link_open(gxio_mpipe_link_t *link, | 
|  | gxio_mpipe_context_t *context, const char *link_name, | 
|  | unsigned int flags) | 
|  | { | 
|  | _gxio_mpipe_link_name_t name; | 
|  | int rv; | 
|  |  | 
|  | if (strscpy(name.name, link_name, sizeof(name.name)) < 0) | 
|  | return GXIO_ERR_NO_DEVICE; | 
|  |  | 
|  | rv = gxio_mpipe_link_open_aux(context, name, flags); | 
|  | if (rv < 0) | 
|  | return rv; | 
|  |  | 
|  | link->context = context; | 
|  | link->channel = rv >> 8; | 
|  | link->mac = rv & 0xFF; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | EXPORT_SYMBOL_GPL(gxio_mpipe_link_open); | 
|  |  | 
|  | int gxio_mpipe_link_close(gxio_mpipe_link_t *link) | 
|  | { | 
|  | return gxio_mpipe_link_close_aux(link->context, link->mac); | 
|  | } | 
|  |  | 
|  | EXPORT_SYMBOL_GPL(gxio_mpipe_link_close); | 
|  |  | 
|  | int gxio_mpipe_link_set_attr(gxio_mpipe_link_t *link, uint32_t attr, | 
|  | int64_t val) | 
|  | { | 
|  | return gxio_mpipe_link_set_attr_aux(link->context, link->mac, attr, | 
|  | val); | 
|  | } | 
|  |  | 
|  | EXPORT_SYMBOL_GPL(gxio_mpipe_link_set_attr); |