diff --git a/T_messages.txt b/T_messages.txt
index ba6197b5697f06ec15cac945aaf4b7571eeb61d1..f954dd46ec26acfa1cf7017e3d1567c94da9238d 100644
--- a/T_messages.txt
+++ b/T_messages.txt
@@ -7,6 +7,14 @@ ID = ENB_UL_CHANNEL_ESTIMATE
     DESC = eNodeB channel estimation in the time domain
     GROUP = PHY:GRAPHIC:HEAVY
     FORMAT = int,eNB_ID : int,UE_ID : int,frame : int,subframe : int,antenna : buffer,chest_t
+ID = PUSCH_IQ
+    DESC = eNodeB PUSCH received IQ data
+    GROUP = PHY:GRAPHIC:HEAVY
+    FORMAT = int,eNB_ID : int,UE_ID : int,frame : int,subframe : int,nb_rb : buffer,pusch_comp
+ID = PUCCH_1AB_IQ
+    DESC = eNodeB PUCCH received IQ data
+    GROUP = PHY:GRAPHIC:HEAVY
+    FORMAT = int,eNB_ID : int,UE_ID : int,frame : int,subframe : int,I : int,Q
 
 #legacy logs
 ID = LEGACY_MAC_INFO
diff --git a/tracer/defs.h b/tracer/defs.h
index 71b9dd0cfb41352f355d2323c7e8b14c1c38996b..873292a70ca7cd39c9b84188838328aa3c472c37 100644
--- a/tracer/defs.h
+++ b/tracer/defs.h
@@ -1,9 +1,15 @@
 #ifndef _TRACER_DEFS_H_
 #define _TRACER_DEFS_H_
 
-void *make_plot(int width, int height, int bufsize, char *title);
+/* types of plots */
+#define PLOT_VS_TIME   0
+#define PLOT_IQ_POINTS 1
+
+void *make_plot(int width, int height, int bufsize, char *title, int type);
 void plot_set(void *plot, float *data, int len, int pos);
 void iq_plot_set(void *plot, short *data, int len, int pos);
+void iq_plot_set_sized(void *_plot, short *data, int len);
+void iq_plot_add_point_loop(void *_plot, short i, short q);
 
 /* returns an opaque pointer - truly a 'database *', see t_data.c */
 void *parse_database(char *filename);
diff --git a/tracer/main.c b/tracer/main.c
index 37774be4e5e33a30040c1519f0042ab9053fa537..853ed46fedf24a87500515f46ed7d326c93c74c0 100644
--- a/tracer/main.c
+++ b/tracer/main.c
@@ -16,6 +16,8 @@
 
 void *ul_plot;
 void *chest_plot;
+void *pusch_iq_plot;
+void *pucch_iq_plot;
 
 #ifdef T_USE_SHARED_MEMORY
 
@@ -236,6 +238,44 @@ void get_message(int s)
     if (chest_plot) iq_plot_set(chest_plot, (short*)buf, 512, 0);
     break;
   }
+  case T_PUSCH_IQ: {
+    unsigned char buf[T_BUFFER_MAX];
+    int size;
+    int eNB, UE, frame, subframe, nb_rb;
+    GET(s, &eNB, sizeof(int));
+    GET(s, &UE, sizeof(int));
+    GET(s, &frame, sizeof(int));
+    GET(s, &subframe, sizeof(int));
+    GET(s, &nb_rb, sizeof(int));
+    GET(s, &size, sizeof(int));
+    GET(s, buf, size);
+    if (size != 12*25*14*4)
+      {printf("bad T_PUSCH_IQ, we want 25 RBs and 14 symbols/TTI\n");
+       abort();}
+    if (pusch_iq_plot) {
+      uint32_t *src, *dst;
+      int i, l;
+      dst = (uint32_t*)buf;
+      for (l = 0; l < 14; l++) {
+        src = (uint32_t*)buf + l * 12 * 25;
+        for (i = 0; i < nb_rb*12; i++) *dst++ = *src++;
+      }
+      iq_plot_set_sized(pusch_iq_plot, (short*)buf, nb_rb*12*14);
+    }
+    break;
+  }
+  case T_PUCCH_1AB_IQ: {
+    int eNB, UE, frame, subframe, I, Q;
+    GET(s, &eNB, sizeof(int));
+    GET(s, &UE, sizeof(int));
+    GET(s, &frame, sizeof(int));
+    GET(s, &subframe, sizeof(int));
+    GET(s, &I, sizeof(int));
+    GET(s, &Q, sizeof(int));
+printf("receiving %d %d\n", I, Q);
+    if (pucch_iq_plot) iq_plot_add_point_loop(pucch_iq_plot, I*10, Q*10);
+    break;
+  }
   case T_buf_test: {
     unsigned char buf[T_BUFFER_MAX];
     int size;
@@ -345,8 +385,11 @@ int main(int n, char **v)
   if (do_dump_database) { dump_database(database); return 0; }
 
   if (do_xforms) {
-    ul_plot = make_plot(512, 100, 7680*2*10, "UL Input Signal");
-    chest_plot = make_plot(512, 100, 512*2, "UL Channel Estimate UE 0");
+    ul_plot = make_plot(512, 100, 7680*10, "UL Input Signal", PLOT_VS_TIME);
+    chest_plot = make_plot(512, 100, 512, "UL Channel Estimate UE 0",
+                           PLOT_VS_TIME);
+    pusch_iq_plot = make_plot(100, 100, 12*25*14, "PUSCH IQ",PLOT_IQ_POINTS);
+    pucch_iq_plot = make_plot(100, 100, 1000, "PUCCH IQ", PLOT_IQ_POINTS);
   }
 
   for (i = 0; i < on_off_n; i++)
diff --git a/tracer/plot.c b/tracer/plot.c
index 3cc0ad069f71e521984db4e20dce6a50fcdbbc84..e13398ab5e5d7fee653a7ae0b89c6a63f0c1119a 100644
--- a/tracer/plot.c
+++ b/tracer/plot.c
@@ -6,6 +6,8 @@
 #include <pthread.h>
 #include <math.h>
 #include <unistd.h>
+#include <pthread.h>
+#include <sys/select.h>
 
 typedef struct {
   Display *d;
@@ -15,50 +17,117 @@ typedef struct {
   int height;
   float *buf;
   short *iqbuf;
-  int bufsize;
+  int count;
+  int type;
+  volatile int iq_count;  /* for ULSCH IQ data */
+  int iq_insert_pos;
+  pthread_mutex_t lock;
+  float zoom;
+  int timer_pipe[2];
 } plot;
 
+static void *timer_thread(void *_p)
+{
+  plot *p = _p;
+  char c;
+
+  while (1) {
+    /* more or less 10Hz */
+    usleep(100*1000);
+    c = 1;
+    if (write(p->timer_pipe[1], &c, 1) != 1) abort();
+  }
+
+  return NULL;
+}
+
 static void *plot_thread(void *_p)
 {
   float v;
   float *s;
   int i, j;
   plot *p = _p;
+  int redraw = 0;
+  int replot = 0;
+  fd_set rset;
+  int xfd = ConnectionNumber(p->d);
+  int maxfd = xfd > p->timer_pipe[0] ? xfd : p->timer_pipe[0];
+
   while (1) {
     while (XPending(p->d)) {
       XEvent e;
       XNextEvent(p->d, &e);
+      switch (e.type) {
+      case ButtonPress:
+        /* button 4: zoom out */
+        if (e.xbutton.button == 4) { p->zoom = p->zoom * 1.25; replot = 1; }
+        /* button 5: zoom in */
+        if (e.xbutton.button == 5) { p->zoom = p->zoom * 0.8; replot = 1; }
+        printf("zoom: %f\n", p->zoom);
+        break;
+      case Expose: redraw = 1; break;
+      }
     }
 
-    {
+    if (replot == 1) {
+      replot = 0;
+      redraw = 1;
+
       /* TODO: get white & black GCs at startup */
-      GC gc;
-      XGCValues v;
-      gc = DefaultGC(p->d, DefaultScreen(p->d));
-      v.foreground = WhitePixel(p->d, DefaultScreen(p->d));
-      XChangeGC(p->d, gc, GCForeground, &v);
-      XFillRectangle(p->d, p->p, gc, 0, 0, p->width, p->height);
-      v.foreground = BlackPixel(p->d, DefaultScreen(p->d));
-      XChangeGC(p->d, gc, GCForeground, &v);
+      {
+        GC gc;
+        XGCValues v;
+        gc = DefaultGC(p->d, DefaultScreen(p->d));
+        v.foreground = WhitePixel(p->d, DefaultScreen(p->d));
+        XChangeGC(p->d, gc, GCForeground, &v);
+        XFillRectangle(p->d, p->p, gc, 0, 0, p->width, p->height);
+        v.foreground = BlackPixel(p->d, DefaultScreen(p->d));
+        XChangeGC(p->d, gc, GCForeground, &v);
+      }
+
+      if (pthread_mutex_lock(&p->lock)) abort();
+
+      if (p->type == PLOT_VS_TIME) {
+        for (i = 0; i < p->count; i++)
+          p->buf[i] = 10*log10(1.0+(float)(p->iqbuf[2*i]*p->iqbuf[2*i]+
+                                           p->iqbuf[2*i+1]*p->iqbuf[2*i+1]));
+        s = p->buf;
+        for (i = 0; i < 512; i++) {
+          v = 0;
+          for (j = 0; j < p->count/512; j++, s++) v += *s;
+          v /= p->count/512;
+          XDrawLine(p->d, p->p, DefaultGC(p->d, DefaultScreen(p->d)),
+                    i, 100, i, 100-v);
+        }
+      } else if (p->type == PLOT_IQ_POINTS) {
+        XPoint pts[p->iq_count];
+        int count = p->iq_count;
+        for (i = 0; i < count; i++) {
+          pts[i].x = p->iqbuf[2*i]*p->zoom/20+50;
+          pts[i].y = -p->iqbuf[2*i+1]*p->zoom/20+50;
+        }
+        XDrawPoints(p->d, p->p, DefaultGC(p->d, DefaultScreen(p->d)),
+                    pts, count, CoordModeOrigin);
+      }
+
+      if (pthread_mutex_unlock(&p->lock)) abort();
     }
 
-  {
-    int i;
-    for (i = 0; i < p->bufsize/2; i++)
-      p->buf[i] = 10*log10(1.0+(float)(p->iqbuf[2*i]*p->iqbuf[2*i]+
-                                       p->iqbuf[2*i+1]*p->iqbuf[2*i+1]));
-  }
-    s = p->buf;
-    for (i = 0; i < 512; i++) {
-      v = 0;
-      for (j = 0; j < p->bufsize/2/512; j++, s++) v += *s;
-      v /= p->bufsize/2/512;
-      XDrawLine(p->d, p->p, DefaultGC(p->d, DefaultScreen(p->d)), i, 100, i, 100-v);
+    if (redraw) {
+      redraw = 0;
+      XCopyArea(p->d, p->p, p->w, DefaultGC(p->d, DefaultScreen(p->d)),
+                0, 0, p->width, p->height, 0, 0);
     }
 
-    XCopyArea(p->d, p->p, p->w, DefaultGC(p->d, DefaultScreen(p->d)),
-              0, 0, p->width, p->height, 0, 0);
-    usleep(100*1000);
+    FD_ZERO(&rset);
+    FD_SET(p->timer_pipe[0], &rset);
+    FD_SET(xfd, &rset);
+    if (select(maxfd+1, &rset, NULL, NULL, NULL) == -1) abort();
+    if (FD_ISSET(p->timer_pipe[0], &rset)) {
+      char b[512];
+      read(p->timer_pipe[0], b, 512);
+      replot = 1;
+    }
   }
 
   return NULL;
@@ -73,13 +142,15 @@ static void new_thread(void *(*f)(void *), void *data)
     { fprintf(stderr, "pthread_attr_init err\n"); exit(1); }
   if (pthread_attr_setdetachstate(&att, PTHREAD_CREATE_DETACHED))
     { fprintf(stderr, "pthread_attr_setdetachstate err\n"); exit(1); }
+  if (pthread_attr_setstacksize(&att, 10000000))
+    { fprintf(stderr, "pthread_attr_setstacksize err\n"); exit(1); }
   if (pthread_create(&t, &att, f, data))
     { fprintf(stderr, "pthread_create err\n"); exit(1); }
   if (pthread_attr_destroy(&att))
     { fprintf(stderr, "pthread_attr_destroy err\n"); exit(1); }
 }
 
-void *make_plot(int width, int height, int bufsize, char *title)
+void *make_plot(int width, int height, int count, char *title, int type)
 {
   plot *p;
   Display *d;
@@ -89,7 +160,7 @@ void *make_plot(int width, int height, int bufsize, char *title)
   d = XOpenDisplay(0); if (d == NULL) abort();
   w = XCreateSimpleWindow(d, DefaultRootWindow(d), 0, 0, width, height,
         0, WhitePixel(d, DefaultScreen(d)), WhitePixel(d, DefaultScreen(d)));
-  XSelectInput(d, w, ExposureMask);
+  XSelectInput(d, w, ExposureMask | ButtonPressMask);
   XMapWindow(d, w);
 
   XStoreName(d, w, title);
@@ -99,15 +170,31 @@ void *make_plot(int width, int height, int bufsize, char *title)
   p = malloc(sizeof(*p)); if (p == NULL) abort();
   p->width = width;
   p->height = height;
-  p->buf = malloc(sizeof(float) * bufsize); if (p->buf == NULL) abort();
-  p->iqbuf = malloc(sizeof(short) * 2 * bufsize); if (p->buf == NULL) abort();
-  p->bufsize = bufsize;
+  if (type == PLOT_VS_TIME) {
+    p->buf = malloc(sizeof(float) * count); if (p->buf == NULL) abort();
+    p->iqbuf = malloc(sizeof(short) * count * 2); if(p->iqbuf==NULL)abort();
+  } else {
+    p->buf = NULL;
+    p->iqbuf = malloc(sizeof(short) * count * 2); if(p->iqbuf==NULL)abort();
+  }
+  p->count = count;
 
   p->d = d;
   p->w = w;
   p->p = pm;
+  p->type = type;
+
+  p->iq_count = 0;
+  p->iq_insert_pos = 0;
+
+  p->zoom = 1;
+
+  pthread_mutex_init(&p->lock, NULL);
+
+  if (pipe(p->timer_pipe)) abort();
 
   new_thread(plot_thread, p);
+  new_thread(timer_thread, p);
 
   return p;
 }
@@ -115,11 +202,36 @@ void *make_plot(int width, int height, int bufsize, char *title)
 void plot_set(void *_plot, float *data, int len, int pos)
 {
   plot *p = _plot;
+  if (pthread_mutex_lock(&p->lock)) abort();
   memcpy(p->buf + pos, data, len * sizeof(float));
+  if (pthread_mutex_unlock(&p->lock)) abort();
+}
+
+void iq_plot_set(void *_plot, short *data, int count, int pos)
+{
+  plot *p = _plot;
+  if (pthread_mutex_lock(&p->lock)) abort();
+  memcpy(p->iqbuf + pos * 2, data, count * 2 * sizeof(short));
+  if (pthread_mutex_unlock(&p->lock)) abort();
+}
+
+void iq_plot_set_sized(void *_plot, short *data, int count)
+{
+  plot *p = _plot;
+  if (pthread_mutex_lock(&p->lock)) abort();
+  memcpy(p->iqbuf, data, count * 2 * sizeof(short));
+  p->iq_count = count;
+  if (pthread_mutex_unlock(&p->lock)) abort();
 }
 
-void iq_plot_set(void *_plot, short *data, int len, int pos)
+void iq_plot_add_point_loop(void *_plot, short i, short q)
 {
   plot *p = _plot;
-  memcpy(p->iqbuf + pos * 2, data, len * 2 * sizeof(short));
+  if (pthread_mutex_lock(&p->lock)) abort();
+  p->iqbuf[p->iq_insert_pos*2] = i;
+  p->iqbuf[p->iq_insert_pos*2+1] = q;
+  if (p->iq_count != p->count) p->iq_count++;
+  p->iq_insert_pos++;
+  if (p->iq_insert_pos == p->count) p->iq_insert_pos = 0;
+  if (pthread_mutex_unlock(&p->lock)) abort();
 }