int main(int argc, char**argv) { // Scan options. char **args = &argv[1]; while (*args) { char *opt = NULL; if ((opt = shift_option(&args, "--test_num="))) g_run_multiplier = atoi(opt); } printf("This test will run %d times.\n", ((1 << LG2_CAPACITY) * g_run_multiplier)); // Make sure we have enough cpus. // if (tmc_cpus_get_dataplane_cpus(&cpus) != 0) if (tmc_cpus_get_my_affinity(&cpus) != 0) tmc_task_die("tmc_cpus_get_my_affinity() failed."); if (tmc_cpus_count(&cpus) < NUM_THREADS) tmc_task_die("Insufficient cpus available."); // Call the main thread function on each cpu, then wait for them all // to exit. run_threads(NUM_THREADS, thread_func); finish_threads(NUM_THREADS); return 0; }
void ssmp_init_platf(int num_procs) { ssmp_num_ues_ = num_procs; //initialize shared memory tmc_cmem_init(0); // Reserve the UDN rectangle that surrounds our cpus. if (tmc_udn_init(&cpus) < 0) tmc_task_die("Failure in 'tmc_udn_init(0)'."); ssmp_barrier = (tmc_sync_barrier_t* ) tmc_cmem_calloc(SSMP_NUM_BARRIERS, sizeof (tmc_sync_barrier_t)); if (ssmp_barrier == NULL) { tmc_task_die("Failure in allocating mem for barriers"); } uint32_t b; for (b = 0; b < SSMP_NUM_BARRIERS; b++) { tmc_sync_barrier_init(ssmp_barrier + b, num_procs); } if (tmc_cpus_count(&cpus) < num_procs) { tmc_task_die("Insufficient cpus (%d < %d).", tmc_cpus_count(&cpus), num_procs); } tmc_task_watch_forked_children(1); }
// Wait for all threads to finish. // void finish_threads(int count) { for (int i = 1; i < count; i++) { if (pthread_join(threads[i], NULL) != 0) tmc_task_die("pthread_join() failed."); } }
void ssmp_term_platf() { if (tmc_udn_close() != 0) { tmc_task_die("Failure in 'tmc_udn_close()'."); } free(udn_header); }
// This method creates (count - 1) child threads and calls 'func' in // each thread, passing the rank to each thread. The caller is // assigned the lowest rank. // void run_threads(int count, void*(*func)(void*)) { for (int i = 1; i < count; i++) { if (pthread_create(&threads[i], NULL, func, (void*)(intptr_t)i) != 0) tmc_task_die("pthread_create() failed."); } (void) func((void*) 0); }
/* * Clear counters for all tiles in cpu set */ int clear_all_counters(cpu_set_t *cpus) { int num_of_cpus = tmc_cpus_count(cpus); for (int i=0;i<num_of_cpus;i++) { if (tmc_cpus_set_my_cpu(tmc_cpus_find_nth_cpu(cpus, i)) < 0) { tmc_task_die("failure in 'tmc_set_my_cpu'"); return -1; } clear_counters(); } return 0; }
/* * Setup counters for all tiles in cpu set */ int setup_all_counters(cpu_set_t *cpus) { int num_of_cpus = tmc_cpus_count(cpus); for (int i=0;i<num_of_cpus;i++) { if (tmc_cpus_set_my_cpu(tmc_cpus_find_nth_cpu(cpus, i)) < 0) { tmc_task_die("failure in 'tmc_set_my_cpu'"); return -1; } clear_counters(); setup_counters(LOCAL_WR_MISS, LOCAL_WR_CNT, LOCAL_DRD_MISS, LOCAL_DRD_CNT); } return 0; }
int main(void) { cpu_set_t cpus; int wr_cnt, wr_miss, drd_cnt, drd_miss; unsigned long start_cycles = get_cycle_count(); // Init cpus if (tmc_cpus_get_my_affinity(&cpus) != 0) { tmc_task_die("Failure in 'tmc_cpus_get_my_affinity()'."); } int num_cpus = tmc_cpus_count(&cpus); printf("cpus_count is: %i\n", num_cpus); // Setup Counters setup_all_counters(&cpus); unsigned long start_for = get_cycle_count(); unsigned long cycles[num_cpus]; unsigned int drd_cnts[num_cpus]; for (int i=0;i<num_cpus;i++) { if (tmc_cpus_set_my_cpu(tmc_cpus_find_nth_cpu(&cpus, i)) < 0) { tmc_task_die("failure in 'tmc_set_my_cpu'"); } read_counters(&wr_cnt, &wr_miss, &drd_cnt, &drd_miss); drd_cnts[i] = drd_cnt; cycles[i] = get_cycle_count(); } unsigned long end_for = get_cycle_count(); for (int i=1;i<num_cpus;i++) { unsigned long temp = cycles[i] - cycles[i-1]; printf("time between %i and %i is %lu\n", i-1, i, temp); printf("drd_cnt for tile %i was %i\n", i, drd_cnts[i]); } printf("Total cycles for-loop: %lu\n", end_for-start_for); return 0; }
void parse_configuration_args(int argc, char** argv) { // Scan options. // char **args = &argv[1]; while (*args) { char *opt = NULL; //TODO handle conflicts between args & conf file ? if ((opt = shift_option(&args, "--conf="))) confFilename = opt; else if ((opt = shift_option(&args, "--ip-conf="))) config_ip_file_name = opt; else if ((opt = shift_option(&args, "--discard"))) packet_drop = 1; #if OFP_LOOP_STATISTICS else if ((opt = shift_option(&args, "--limit-packets="))) limit_packets = strtoul(opt,NULL,0); #endif else if ((opt = shift_option(&args, "--workers="))) work_size = atoi(opt); #if TILEGX else if ((opt = shift_option(&args, "--links="))) parse_links(0, opt); #else #if TWOINTERFACE else if ((opt = shift_option(&args, "--interface1="))) interface1 = opt; else if ((opt = shift_option(&args, "--interface2="))) interface2 = opt; #else else if ((opt = shift_option(&args, "--interface="))) interface1 = opt; #endif #endif #if MODE_VLAN else if ((opt = shift_option(&args, "--vlan1="))) packet_vlan_swap1 = strtoul(opt,NULL,0); else if ((opt = shift_option(&args, "--vlan2="))) packet_vlan_swap2 = strtoul(opt,NULL,0); #endif else if ((opt = shift_option(&args, "--daemon"))) config_daemonize = 1; else tmc_task_die("Unknown option '%s'.\n", args[0]); } }
void set_cpu_platf(int cpu) { ssmp_my_core = cpu; if (tmc_cpus_set_my_cpu(tmc_cpus_find_nth_cpu(&cpus, cpu)) < 0) { tmc_task_die("Failure in 'tmc_cpus_set_my_cpu()'."); } if (cpu != tmc_cpus_get_my_cpu()) { PRINT("******* i am not CPU %d", tmc_cpus_get_my_cpu()); } }
void ssmp_mem_init_platf(int id, int num_ues) { ssmp_id_ = id; ssmp_num_ues_ = num_ues; // Now that we're bound to a core, attach to our UDN rectangle. if (tmc_udn_activate() < 0) tmc_task_die("Failure in 'tmc_udn_activate()'."); udn_header = (DynamicHeader* ) memalign(SSMP_CACHE_LINE_SIZE, num_ues * sizeof (DynamicHeader)); if (udn_header == NULL) { tmc_task_die("Failure in allocating dynamic headers"); } int r; for (r = 0; r < num_ues; r++) { int _cpu = tmc_cpus_find_nth_cpu(&cpus, id_to_core[r]); DynamicHeader header = tmc_udn_header_from_cpu(_cpu); udn_header[r] = header; } }
// The worker function for each thread. The sender injects many items // into the queue, and the receiver consumes them. // void* thread_func(void* arg) { int rank = (intptr_t) arg; int capacity = 1 << LG2_CAPACITY; int count = capacity * g_run_multiplier; // Bind this thread to the rank'th core in the cpu set. if (tmc_cpus_set_my_cpu(tmc_cpus_find_nth_cpu(&cpus, rank)) < 0) tmc_task_die("tmc_cpus_set_my_cpu() failed."); if (rank == 1) { queue = memalign(64, sizeof(*queue)); assert(queue != NULL); my_queue_init(queue); } tmc_spin_barrier_wait(&barrier); // Single obj enqueue. uint64_t cycles; if (rank == 0) cycles = bench_sender(count); else cycles = bench_receiver(count); uint64_t reverse_cycles; if (rank == 1) reverse_cycles = bench_sender(count); else reverse_cycles = bench_receiver(count); if (rank == 0) { printf("One-to-one cycles per transfer: %0.2f\n", (float) cycles / count); printf("One-to-one cycles per transfer (reverse): %0.2f\n", (float) reverse_cycles / count); } // Multiple obj enqueue. if (rank == 0) cycles = bench_sender_multiple(count); else cycles = bench_receiver(count); if (rank == 1) reverse_cycles = bench_sender_multiple(count); else reverse_cycles = bench_receiver(count); if (rank == 0) { printf("Multiple one-to-one cycles per transfer: %0.2f\n", (float) cycles / count); printf("Multiple one-to-one cycles per transfer (reverse): %0.2f\n", (float) reverse_cycles / count); } return NULL; }
/** Main function. */ int main(int argc, char** argv) { // Number of instances of this program to run // (including the initial parent process). int instances = 4; // Detect whether we're the parent or an exec'd child, int is_parent = is_parent_process(); // Get the application's affinity set. // We'll use the first N available cpus from this set. // NOTE: this means parent should _not_ call any functions // that shrink the affinity set prior to go_parellel(); cpu_set_t cpus; int status = tmc_cpus_get_my_affinity(&cpus); check_tmc_status(status, "tmc_cpus_get_my_affinity()"); // Define UDN cpu set as first N available cpus status = udn_init(instances, &cpus); check_tmc_status(status, "udn_init()"); // Initialize "common" shared memory with default size. status = tmc_cmem_init(0); check_tmc_status(status, "tmc_cmem_init()"); // Allocate barrier data structure in shared memory. tmc_sync_barrier_t* barrier = NULL; if (is_parent) { // Allocate/initialize barrier data structure in common memory. barrier = (tmc_sync_barrier_t*) tmc_cmem_malloc(sizeof(*barrier)); if (barrier == NULL) tmc_task_die("barrier_init(): " "Failed to allocate barrier data structure."); tmc_sync_barrier_init(barrier, instances); } // Pass the barrier pointer to any exec'd children. share_pointer("SHARED_BARRIER_POINTER", (void**) &barrier); // Fork/exec any additional child processes, // each locked to its own tile, // and get index [0 -- instances-1] of current process. int index = go_parallel(instances, &cpus, argc, argv); pid_t pid = getpid(); printf("Process(pid=%i), index=%i: started.\n", pid, index); // Enable UDN access for this process (parent or child). // Note: this needs to be done after we're locked to a tile. status = tmc_udn_activate(); check_tmc_status(status, "tmc_udn_activate()"); // Wait here until all other processes have caught up. tmc_sync_barrier_wait(barrier); // Send/receive a value over the UDN. int from = 0; int to = instances - 1; if (index == from) { int value = 42; printf("Process(pid=%i), index=%i: sending value %i to cpu %i...\n", pid, index, value, to); udn_send_to_nth_cpu(to, &cpus, value); printf("Process(pid=%i), index=%i: sent value %i to cpu %i.\n", pid, index, value, to); } else if (index == to) { int received = 0; printf("Process(pid=%i), index=%i: receiving value...\n", pid, index); received = udn_receive(); printf("Process(pid=%i), index=%i: received value %i...\n", pid, index, received); } // Wait here until all other processes have caught up. tmc_sync_barrier_wait(barrier); printf("Process(pid=%i), index=%i: finished.\n", pid, index); // We're done. return 0; }
int main(int argc, char** argv) { char *link_name= "xgbe1"; size_t num_packets = 1000; int instance; int result; for (int i = 1; i < argc; i++){ char* arg = argv[i]; if (!strcmp(arg, "--link") && i + 1 < argc) { link_name = argv[++i]; } else if (!strcmp(arg, "-n") && i + 1 < argc) { num_packets = atoi(argv[++i]); } else if ((!strcmp(arg,"-s")) || (!strcmp(arg,"-l"))) { server = 1; } else if (!strcmp(arg,"--jumbo")) { jumbo = true; } else if ((!strcmp(arg,"-c"))) { server = 0; } else { tmc_task_die("Unknown option '%s'.", arg); } } printf("\n finished parsing"); if (server) printf("\n link egressing is %s", link_name); else printf("\n link ingressing is %s", link_name); // Get the instance. instance = gxio_mpipe_link_instance(link_name); if (instance < 0) tmc_task_die("Link '%s' does not exist.", link_name); gxio_mpipe_context_t context_body; gxio_mpipe_context_t* const context = &context_body; gxio_mpipe_iqueue_t iqueue_body; gxio_mpipe_iqueue_t* iqueue = &iqueue_body; gxio_mpipe_equeue_t equeue_body; gxio_mpipe_equeue_t* const equeue = &equeue_body; // Bind to a single cpu. cpu_set_t cpus; result = tmc_cpus_get_my_affinity(&cpus); VERIFY(result, "tmc_cpus_get_my_affinity()"); result = tmc_cpus_set_my_cpu(tmc_cpus_find_first_cpu(&cpus)); VERIFY(result, "tmc_cpus_set_my_cpu()"); // Start the driver. result = gxio_mpipe_init(context, instance); VERIFY(result, "gxio_mpipe_init()"); gxio_mpipe_link_t link; if (!server) { result = gxio_mpipe_link_open(&link, context, link_name, 0); } else { result = gxio_mpipe_link_open(&link, context, link_name, GXIO_MPIPE_LINK_WAIT ); } VERIFY(result, "gxio_mpipe_link_open()"); int channel = gxio_mpipe_link_channel(&link); //allow the link to receive jumbo packets if (jumbo) gxio_mpipe_link_set_attr(&link, GXIO_MPIPE_LINK_RECEIVE_JUMBO, 1); // Allocate a NotifRing. result = gxio_mpipe_alloc_notif_rings(context, 1, 0, 0); VERIFY(result, "gxio_mpipe_alloc_notif_rings()"); int ring = result; // Allocate one huge page to hold our buffer stack, notif ring, and group tmc_alloc_t alloc = TMC_ALLOC_INIT; tmc_alloc_set_huge(&alloc); tmc_alloc_set_home(&alloc, tmc_cpus_find_nth_cpu(&cpus, 0)); size_t page_size = tmc_alloc_get_huge_pagesize(); void* page = tmc_alloc_map(&alloc, page_size); assert(page!= NULL); void* mem = page; // Init the NotifRing. size_t notif_ring_entries = 128; size_t notif_ring_size = notif_ring_entries * sizeof(gxio_mpipe_idesc_t); result = gxio_mpipe_iqueue_init(iqueue, context, ring, mem, notif_ring_size, 0); VERIFY(result, "gxio_mpipe_iqueue_init()"); mem += notif_ring_size; // Allocate a NotifGroup. result = gxio_mpipe_alloc_notif_groups(context, 1, 0, 0); VERIFY(result, "gxio_mpipe_alloc_notif_groups()"); int group = result; // Allocate a bucket. int num_buckets = 128; result = gxio_mpipe_alloc_buckets(context, num_buckets, 0, 0); VERIFY(result, "gxio_mpipe_alloc_buckets()"); int bucket = result; // Init group and bucket. gxio_mpipe_bucket_mode_t mode = GXIO_MPIPE_BUCKET_DYNAMIC_FLOW_AFFINITY; result = gxio_mpipe_init_notif_group_and_buckets(context, group, ring, 1, bucket, num_buckets, mode); VERIFY(result, "gxio_mpipe_init_notif_group_and_buckets()"); // Alloc edma rings result = gxio_mpipe_alloc_edma_rings(context, 1, 0, 0); VERIFY(result, "gxio_mpipe_alloc_edma_rings"); int edma = result; // Init edma ring. int edma_ring_entries = 512; size_t edma_ring_size = edma_ring_entries * sizeof(gxio_mpipe_edesc_t); result = gxio_mpipe_equeue_init(equeue, context, edma, channel, mem, edma_ring_size, 0); VERIFY(result, "gxio_mpipe_equeue_init()"); mem += edma_ring_size; // Allocate a buffer stack. result = gxio_mpipe_alloc_buffer_stacks(context, 1, 0, 0); VERIFY(result, "gxio_mpipe_alloc_buffer_stacks()"); int stack_idx = result; // Total number of buffers. unsigned int num_buffers = (int)(edma_ring_entries + notif_ring_entries); // Initialize the buffer stack. Must be aligned mod 64K. ALIGN(mem, 0x10000); size_t stack_bytes = gxio_mpipe_calc_buffer_stack_bytes(num_buffers); gxio_mpipe_buffer_size_enum_t buf_size = GXIO_MPIPE_BUFFER_SIZE_16384; result = gxio_mpipe_init_buffer_stack(context, stack_idx, buf_size, mem, stack_bytes, 0); VERIFY(result, "gxio_mpipe_init_buffer_stack()"); mem += stack_bytes; ALIGN(mem, 0x10000); // Register the entire huge page of memory which contains all the buffers. result = gxio_mpipe_register_page(context, stack_idx, page, page_size, 0); VERIFY(result, "gxio_mpipe_register_page()"); // Push some buffers onto the stack. for (int i = 0; i < num_buffers; i++) { gxio_mpipe_push_buffer(context, stack_idx, mem); mem += 16384; } // Register for packets. gxio_mpipe_rules_t rules; gxio_mpipe_rules_init(&rules, context); gxio_mpipe_rules_begin(&rules, bucket, num_buckets, NULL); result = gxio_mpipe_rules_commit(&rules); VERIFY(result, "gxio_mpipe_rules_commit()"); double start, end, exec_time, throughput; start = 0.00; uint64_t cpu_speed; cpu_speed = tmc_perf_get_cpu_speed(); /*Server will initiate the egress and ingress the packets and display the round trip time * Client will ingress the packet, copy it to the edesc and egress it */ if (server) { int send_packets = 0; size_t size_e = 0; struct timespec req_start, req_end; while (send_packets < num_packets) { char* buf = gxio_mpipe_pop_buffer(context, stack_idx); if(buf == NULL) tmc_task_die("Could not allocate initial buffer"); memset(buf,'+',PACKET_SIZE); // Prepare to egress the packet. gxio_mpipe_edesc_t edesc = {{ .bound = 1, .xfer_size = PACKET_SIZE, .stack_idx = stack_idx, .hwb = 1, .size = GXIO_MPIPE_BUFFER_SIZE_16384 }}; gxio_mpipe_edesc_set_va(&edesc, buf); result = gxio_mpipe_equeue_put(equeue, edesc); VERIFY(result, "gxio_mpipe_equeue_put()"); if (send_packets == 0) clock_gettime(CLOCK_REALTIME, &req_start); gxio_mpipe_idesc_t idesc; result = gxio_mpipe_iqueue_get(iqueue,&idesc); VERIFY(result, "gxio_mpipe_iqueue_get()"); size_e += idesc.l2_size; gxio_mpipe_iqueue_drop(iqueue, &idesc); gxio_mpipe_equeue_flush(equeue); send_packets++; } clock_gettime(CLOCK_REALTIME, &req_end); exec_time = ((req_end.tv_sec - req_start.tv_sec)+(req_end.tv_nsec - req_start.tv_nsec)/1E9); fprintf(stdout,"round trip time = %lf\n", exec_time); fprintf(stdout,"latency is %f\n", exec_time/(2 * num_packets )); fprintf(stdout,"size is %zd b\n", size_e); throughput = size_e * 8 * 2 / exec_time; fprintf(stdout,"throughput = %f Mbps\n",throughput/pow(1000, 2)); gxio_mpipe_edesc_t ns = {{ .ns = 1 }}; result = gxio_mpipe_equeue_put(equeue,ns); VERIFY(result, "gxio_mpipe_equeue_put()"); fprintf(stdout,"completed packets %d\n", send_packets); } else {
// reload : set to 1 if we are reloading the conf while running // When we are reloading, only some options can be changed. E.g. 'workers' can not, it needs a restart void parse_main_configuration_file(int reload) { if (confFilename == NULL) { PRINT_D2("No config file provided\n"); return; } FILE *confFile = fopen(confFilename, "r"); if (confFile == NULL) tmc_task_die("Error opening config file %s\n", confFilename); PRINT_INFO("Loading config file %s\n", confFilename); char * line = NULL; size_t len = 0; ssize_t read; int lineCount = 0; while ((read = getline(&line, &len, confFile)) != -1) { lineCount++; int lineNumber = lineCount; // Ignore empty lines and lines starting with '#' if (strcmp(line, "\n") == 0 || line[0] == '#') continue; // Duplicate line since strtok modifies its arg char *dup_line = strdup(line); char *param = strtok(dup_line, "="); char *value = strtok(NULL, "\n"); if (value == NULL) tmc_task_die("Error parsing config file(%d), param '%s' has no value\n", lineNumber, param); if (!reload && !strcmp(param, "workers")) work_size = atoi(value); #if TILEGX else if (!reload && !strcmp(param, "links")) parse_links(lineNumber, value); #endif else if (!strcmp(param, "monitoring_ip_port")) { ip_port_tuple ip_port = {0}; if(!parse_ip(value, 0, &ip_port)) { tmc_task_die("Error parsing config file(%d), param '%s' parsing ip '%s'\n", lineNumber, param, value); } ofp_logger_addr.sin_port = htons(ip_port.port); ofp_logger_addr.sin_addr.s_addr = htonl(ip_port.ip); } else if (!strcmp(param, "bridge_mode") && !reload) { PRINT_D5("read bridge_mode = '%s'\n", value); config_bridge_mode = atoi(value); } OVH_FREE(dup_line); } if (line != NULL) OVH_FREE(line); fclose(confFile); PRINT_INFO("config_bridge_mode = %d\n", config_bridge_mode); }
void* net_thread(void* arg) { int iix = (uintptr_t)arg; /*Ingress interface index*/ int eix; /*Egress interface index*/ int i, n; /*Index, Number*/ gxio_mpipe_iqueue_t *iqueue = iqueues[iix]; /*Ingress queue*/ gxio_mpipe_equeue_t *equeue; /*Egress queue*/ gxio_mpipe_idesc_t *idescs; /*Ingress packet descriptors*/ gxio_mpipe_edesc_t edescs[MAXBATCH]; /*Egress descriptors.*/ long slot; /*Setup egress queue.*/ switch (iix) { case 0: eix = 1; break; case 1: eix = 0; break; case 2: eix = 3; break; case 3: eix = 2; break; default: tmc_task_die("Invalid interface index, %d", iix); break; } equeue = &equeues[eix]; /*Egress queue*/ /*Bind to a single CPU.*/ if (tmc_cpus_set_my_cpu(tmc_cpus_find_nth_cpu(&cpus, DTILEBASE + iix)) < 0) { tmc_task_die("Failed to setup CPU affinity\n"); } if (set_dataplane(0) < 0) { tmc_task_die("Failed to setup dataplane\n"); } /*Line up all network threads.*/ tmc_sync_barrier_wait(&syncbar); tmc_spin_barrier_wait(&spinbar); if (iix == 0) { /*Pause briefly, to let everyone finish passing the barrier.*/ for (i = 0; i < 10000; i++) __insn_mfspr(SPR_PASS); /*Allow packets to flow (on all links).*/ sim_enable_mpipe_links(mpipei, -1); } /*-------------------------------------------------------------------------*/ /* Process(forward) packets. */ /*-------------------------------------------------------------------------*/ while (1) { /*Receive packet(s).*/ n = gxio_mpipe_iqueue_peek(iqueue, &idescs); if (n <= 0) continue; else if (n > 16) n = 16; //TODO: Experiment with this number. #if 0 printf("[%d] Get packet(s), n=%d\n", iix, n); #endif /*Prefetch packet descriptors from L3 to L1.*/ tmc_mem_prefetch(idescs, n * sizeof(*idescs)); /*Reserve slots. NOTE: This might spin.*/ slot = gxio_mpipe_equeue_reserve_fast(equeue, n); /*Process packet(s).*/ for (i = 0; i < n; i++) { /*Detect Call(s), clone the packet and pass it to antother Tile, if necessary.*/ //TODO: For now, inspect and record the packet using this Tile. if (ccap_detect_call(&idescs[i])) { ccap_trace_add(0, &idescs[i]); //TODO: Use actual link number. } /*Send the packets out on the peer port.*/ gxio_mpipe_edesc_copy_idesc(&edescs[i], &idescs[i]); #if 1 /*Drop "error" packets (but ignore "checksum" problems).*/ if (idescs[i].be || idescs[i].me || idescs[i].tr || idescs[i].ce) { edescs[i].ns = 1; } #endif gxio_mpipe_equeue_put_at(equeue, edescs[i], slot + i); gxio_mpipe_iqueue_consume(iqueue, &idescs[i]); } } /*Make compiler happy.*/ return (void *)NULL; }