#include <bits/stdc++.h>
using namespace std;
long long p = 1000000007;
long long max_kai = 10000;
vector<long long> kai(max_kai, 1);
vector<long long> inv_kai(max_kai, 1);
long long rui(long long a, long long n, long long mod) {
  if (n == 0) {
    return 1 % mod;
  }
  long long x = rui(a, n / 2, mod);
  x *= x;
  x %= mod;
  if (n % 2 == 1) {
    x *= a;
    x %= mod;
  }
  return x;
}
long long root(long long x, vector<long long>& pa) {
  if (pa[x] == -1) {
    return x;
  }
  long long ans = root(pa[x], pa);
  pa[x] = ans;
  return ans;
}
bool mar(long long x, long long y, vector<long long>& pa) {
  x = root(x, pa);
  y = root(y, pa);
  if (x != y) {
    pa[x] = y;
  }
  return (x != y);
}
long long gcd(long long x, long long y) {
  if (x < y) {
    return gcd(y, x);
  }
  if (y == 0) {
    return x;
  }
  return gcd(y, x % y);
}
long long lcm(long long x, long long y) {
  x = abs(x);
  y = abs(y);
  return x * y / gcd(x, y);
}
long long modinv(long long a, long long m) {
  long long b = m, u = 1, v = 0;
  while (b) {
    long long t = a / b;
    a -= t * b;
    swap(a, b);
    u -= t * v;
    swap(u, v);
  }
  u %= m;
  if (u < 0) u += m;
  return u;
}
void make_kai() {
  for (long long i = 0; i < max_kai - 1; i++) {
    kai[i + 1] = kai[i] * (i + 1);
    kai[i + 1] %= p;
    inv_kai[i + 1] = modinv(kai[i + 1], p);
  }
}
long long com(long long n, long long r) {
  if ((n < 0) || (r < 0) || (r > n)) {
    return 0;
  }
  long long ans = (kai[n] * inv_kai[r]) % p;
  return (ans * inv_kai[n - r]) % p;
}
vector<long long> uni(vector<long long> x) {
  if (x.size() == 0) {
    return x;
  }
  sort(x.begin(), x.end());
  long long n = x.size();
  vector<long long> ans(1, x[0]);
  for (long long j = 1; j < n; j++) {
    if (x[j - 1] != x[j]) {
      ans.push_back(x[j]);
    }
  }
  x = ans;
  return x;
}
void pr(vector<long long>& v) {
  long long n = v.size();
  if (n != 0) {
    cout << v[0];
    for (long long i = 0; i < n - 1; i++) {
      cout << " " << v[i + 1];
    }
    cout << endl;
  }
}
vector<long long> dijk(vector<vector<pair<long long, long long>>>& way,
                       long long st, long long inf) {
  long long n = way.size();
  vector<long long> dist(n, inf);
  dist[st] = 0;
  priority_queue<pair<long long, long long>, vector<pair<long long, long long>>,
                 greater<pair<long long, long long>>>
      pq;
  pq.push(make_pair(0, st));
  vector<bool> is_checked(n, false);
  while (!pq.empty()) {
    pair<long long, long long> x = pq.top();
    pq.pop();
    long long pot = x.second;
    if (!is_checked[pot]) {
      is_checked[pot] = true;
      for (auto y : way[pot]) {
        long long nex_dist = x.first + y.second;
        long long nex_pot = y.first;
        if (dist[nex_pot] > nex_dist) {
          dist[nex_pot] = nex_dist;
          pq.push(make_pair(nex_dist, y.first));
        }
      }
    }
  }
  return dist;
}
template <typename Monoid>
struct SegmentTree {
  using F = function<Monoid(Monoid, Monoid)>;
  long long sz;
  vector<Monoid> seg;
  const F f;
  const Monoid M1;
  SegmentTree(long long n, const F f, const Monoid& M1) : f(f), M1(M1) {
    sz = 1;
    while (sz < n) sz <<= 1;
    seg.assign(2 * sz, M1);
  }
  void set(long long k, const Monoid& x) { seg[k + sz] = x; }
  void build() {
    for (long long k = sz - 1; k > 0; k--) {
      seg[k] = f(seg[2 * k + 0], seg[2 * k + 1]);
    }
  }
  void update(long long k, const Monoid& x) {
    k += sz;
    seg[k] = x;
    while (k >>= 1) {
      seg[k] = f(seg[2 * k + 0], seg[2 * k + 1]);
    }
  }
  Monoid query(long long a, long long b) {
    Monoid L = M1, R = M1;
    for (a += sz, b += sz; a < b; a >>= 1, b >>= 1) {
      if (a & 1) L = f(L, seg[a++]);
      if (b & 1) R = f(seg[--b], R);
    }
    return f(L, R);
  }
  Monoid operator[](const long long& k) const { return seg[k + sz]; }
  template <typename C>
  long long find_subtree(long long a, const C& check, Monoid& M, bool type) {
    while (a < sz) {
      Monoid nxt = type ? f(seg[2 * a + type], M) : f(M, seg[2 * a + type]);
      if (check(nxt))
        a = 2 * a + type;
      else
        M = nxt, a = 2 * a + 1 - type;
    }
    return a - sz;
  }
  template <typename C>
  long long find_first(long long a, const C& check) {
    Monoid L = M1;
    if (a <= 0) {
      if (check(f(L, seg[1]))) return find_subtree(1, check, L, false);
      return -1;
    }
    long long b = sz;
    for (a += sz, b += sz; a < b; a >>= 1, b >>= 1) {
      if (a & 1) {
        Monoid nxt = f(L, seg[a]);
        if (check(nxt)) return find_subtree(a, check, L, false);
        L = nxt;
        ++a;
      }
    }
    return -1;
  }
  template <typename C>
  long long find_last(long long b, const C& check) {
    Monoid R = M1;
    if (b >= sz) {
      if (check(f(seg[1], R))) return find_subtree(1, check, R, true);
      return -1;
    }
    long long a = sz;
    for (b += sz; a < b; a >>= 1, b >>= 1) {
      if (b & 1) {
        Monoid nxt = f(seg[--b], R);
        if (check(nxt)) return find_subtree(b, check, R, true);
        R = nxt;
      }
    }
    return -1;
  }
};
vector<long long> mul(vector<long long>& a, vector<long long>& b) {
  long long n = a.size();
  long long m = b.size();
  vector<long long> ans(n + m - 1, 0);
  for (long long i = 0; i < n; i++) {
    for (long long j = 0; j < m; j++) {
      ans[i + j] += a[i] * b[j];
      ans[i + j] %= p;
    }
  }
  return ans;
}
vector<long long> rui_p(vector<long long>& a, long long n) {
  if (n == 0) {
    return {1};
  }
  vector<long long> qans = rui_p(a, n / 2);
  qans = mul(qans, qans);
  if (n % 2 == 1) {
    qans = mul(qans, a);
  }
  return qans;
}
vector<vector<long long>> disj_min(vector<long long>& v) {
  long long n = v.size();
  vector<vector<long long>> ret(22, vector<long long>(n));
  ret[0] = v;
  for (long long i = 0; i < 21; i++) {
    for (long long j = 0; j < n; j++) {
      long long nex = j + (1 << i);
      if (nex < n) {
        ret[i + 1][j] = min(ret[i][j], ret[i][nex]);
      } else {
        ret[i + 1][j] = ret[i][j];
      }
    }
  }
  return ret;
}
vector<vector<long long>> disj_max(vector<long long>& v) {
  long long n = v.size();
  vector<vector<long long>> ret(20, vector<long long>(n));
  ret[0] = v;
  for (long long i = 0; i < 19; i++) {
    for (long long j = 0; j < n; j++) {
      long long nex = j + (1 << i);
      if (nex < n) {
        ret[i + 1][j] = max(ret[i][j], ret[i][nex]);
      } else {
        ret[i + 1][j] = ret[i][j];
      }
    }
  }
  return ret;
}
long long find_min(vector<vector<long long>>& dv, long long l, long long r) {
  long long i = 19;
  while (l + (1 << i) > r) {
    i--;
  }
  return min(dv[i][l], dv[i][r - (1 << i)]);
}
long long find_max(vector<vector<long long>>& dv, long long l, long long r) {
  long long i = 19;
  while (l + (1 << i) > r) {
    i--;
  }
  return max(dv[i][l], dv[i][r - (1 << i)]);
}
bool is_prime(long long n) {
  if (n == 0 || n == 1) {
    return false;
  }
  for (long long i = 2; i * i <= n; i++) {
    if (n % i == 0) {
      return false;
    }
  }
  return true;
}
signed main() {
  long long h, w;
  cin >> h >> w;
  vector<vector<bool>> is_wall(h, vector<bool>(w));
  for (long long i = 0; i < h; i++) {
    string s;
    cin >> s;
    for (long long j = 0; j < w; j++) {
      is_wall[i][j] = (s[j] == '#');
    }
  }
  vector<vector<pair<long long, long long>>> way(h * w);
  for (long long i = 0; i < h - 1; i++) {
    for (long long j = 0; j < w; j++) {
      long long mi = i + 1;
      if (!is_wall[i][j] && !is_wall[mi][j]) {
        way[i * w + j].push_back(make_pair(mi * w + j, 1));
        way[mi * w + j].push_back(make_pair(i * w + j, 1));
      }
    }
  }
  for (long long i = 0; i < h; i++) {
    for (long long j = 0; j < w - 1; j++) {
      long long mj = j + 1;
      if (!is_wall[i][j] && !is_wall[i][mj]) {
        way[i * w + j].push_back(make_pair(i * w + mj, 1));
        way[i * w + mj].push_back(make_pair(i * w + j, 1));
      }
    }
  }
  long long ans = 0;
  for (long long i = 0; i < h; i++) {
    for (long long j = 0; j < w; j++) {
      if (!is_wall[i][j]) {
        vector<long long> dist = dijk(way, i * w + j, 80);
        for (long long mi = 0; mi < h; mi++) {
          for (long long mj = 0; mj < w; mj++) {
            if (!is_wall[mi][mj]) {
              ans = max(ans, dist[mi * w + mj]);
            }
          }
        }
      }
    }
  }
  cout << ans << endl;
  return 0;
}
